618 lines
35 KiB
PHP
618 lines
35 KiB
PHP
<div class="mt-12">
|
|
|
|
<!-- Header -->
|
|
<div class="mb-6">
|
|
<h2 class="text-lg font-semibold text-theme-primary">{{ __('Admin Roles') }}</h2>
|
|
<p class="mt-1 text-sm text-theme-secondary">{{ __('Manage roles that can be assigned to admin profiles. System roles cannot be modified.') }}</p>
|
|
</div>
|
|
|
|
<!-- Create button -->
|
|
@can('create roles')
|
|
<div class="mb-6">
|
|
<x-jetstream.button wire:click="openCreateModal" class="bg-theme-brand hover:bg-opacity-80">
|
|
<x-icon name="plus" class="mr-2 h-4 w-4" />
|
|
{{ __('Create Role') }}
|
|
</x-jetstream.button>
|
|
</div>
|
|
@endcan
|
|
|
|
<!-- Search -->
|
|
<div class="mb-6 flex items-center gap-3">
|
|
<div class="relative w-2/3">
|
|
<input
|
|
class="w-full rounded-md border border-theme-primary px-3 py-1 pr-10 text-theme-primary shadow-sm focus:border-theme-accent focus:outline-none focus:ring-1 focus:ring-theme-accent sm:text-sm"
|
|
placeholder="{{ __('Search roles') }}..."
|
|
type="text"
|
|
wire:model.live.debounce.300ms="search">
|
|
@if ($search)
|
|
<button
|
|
class="absolute inset-y-0 right-0 flex items-center pr-3 text-theme-secondary hover:text-theme-primary focus:outline-none"
|
|
wire:click.prevent="resetSearch">
|
|
<x-icon mini name="backspace" solid />
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="overflow-hidden rounded-lg bg-white shadow-sm">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 cursor-pointer"
|
|
wire:click="sortBy('label')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>{{ __('Label') }}</span>
|
|
@if ($sortField === 'label')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 cursor-pointer"
|
|
wire:click="sortBy('name')">
|
|
<div class="flex items-center space-x-1">
|
|
<span>{{ __('Name') }}</span>
|
|
@if ($sortField === 'name')
|
|
<x-icon :name="$sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'" class="h-4 w-4" micro />
|
|
@endif
|
|
</div>
|
|
</th>
|
|
<th class="hidden xl:table-cell px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
|
{{ __('Permissions') }}
|
|
</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
|
{{ __('Type') }}
|
|
</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">
|
|
{{ __('Actions') }}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 bg-white">
|
|
@forelse ($roles as $role)
|
|
<tr class="hover:bg-gray-50 align-top" wire:key="role-{{ $role->id }}">
|
|
<td class="px-4 py-3 text-sm text-gray-900">
|
|
{{ $role->label }}
|
|
</td>
|
|
<td class="px-4 py-3 text-sm text-gray-700">
|
|
{{ $role->name }}
|
|
</td>
|
|
<td class="hidden xl:table-cell px-4 py-3 text-xs text-gray-500 leading-relaxed">
|
|
@php
|
|
$perms = $role->permissions ?? [];
|
|
$managedResources = collect($perms)
|
|
->filter(fn($p) => str_starts_with($p, 'manage '))
|
|
->map(fn($p) => substr($p, 7))
|
|
->all();
|
|
$displayed = collect($perms)->filter(function ($p) use ($managedResources) {
|
|
if (!str_contains($p, ' ')) return true;
|
|
[$verb, $resource] = explode(' ', $p, 2);
|
|
return $verb === 'manage' || !in_array($resource, $managedResources);
|
|
})->values()->all();
|
|
@endphp
|
|
{{ implode(', ', $displayed) }}
|
|
</td>
|
|
<td class="px-4 py-3 text-sm">
|
|
@if ($role->is_system)
|
|
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">
|
|
{{ __('System') }}
|
|
</span>
|
|
@else
|
|
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">
|
|
{{ __('Custom') }}
|
|
</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-3 text-right">
|
|
<div class="flex justify-end space-x-2">
|
|
@can('update roles')
|
|
<x-jetstream.secondary-button
|
|
title="{{ __('Edit') }}"
|
|
wire:click="openEditModal({{ $role->id }})">
|
|
<x-icon name="pencil-square" class="h-5 w-5" />
|
|
</x-jetstream.secondary-button>
|
|
@endcan
|
|
@can('delete roles')
|
|
@if (!$role->is_system)
|
|
<x-jetstream.danger-button
|
|
title="{{ __('Delete') }}"
|
|
wire:click="openDeleteModal({{ $role->id }})">
|
|
<x-icon name="trash" class="h-5 w-5" />
|
|
</x-jetstream.danger-button>
|
|
@endif
|
|
@endcan
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">
|
|
{{ __('No roles found.') }}
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
@if ($roles->hasPages())
|
|
<div class="border-t border-gray-200 px-4 py-3">
|
|
{{ $roles->links() }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
@php
|
|
$roleTableHeaders = ['Label', 'Name', 'Permissions', 'Type', 'Actions'];
|
|
@endphp
|
|
|
|
<!-- Organization Roles -->
|
|
<div class="mt-12 mb-6">
|
|
<h2 class="text-lg font-semibold text-theme-primary">{{ __('Organization Roles') }}</h2>
|
|
<p class="mt-1 text-sm text-theme-secondary">{{ __('Roles assigned to users linked to organization profiles. System roles cannot be deleted.') }}</p>
|
|
</div>
|
|
|
|
@can('create roles')
|
|
<div class="mb-6">
|
|
<x-jetstream.button wire:click="openCreateOrgRoleModal('organization')" class="bg-theme-brand hover:bg-opacity-80">
|
|
<x-icon name="plus" class="mr-2 h-4 w-4" />
|
|
{{ __('Create Organization Role') }}
|
|
</x-jetstream.button>
|
|
</div>
|
|
@endcan
|
|
|
|
<div class="overflow-hidden rounded-lg bg-white shadow-sm">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Label') }}</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Name') }}</th>
|
|
<th class="hidden xl:table-cell px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Permissions') }}</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Type') }}</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Actions') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 bg-white">
|
|
@forelse ($orgRoles as $orgRole)
|
|
<tr class="hover:bg-gray-50 align-top" wire:key="org-role-{{ $orgRole->id }}">
|
|
<td class="px-4 py-3 text-sm text-gray-900">{{ $orgRole->label }}</td>
|
|
<td class="px-4 py-3 text-sm text-gray-700">{{ $orgRole->name }}</td>
|
|
<td class="hidden xl:table-cell px-4 py-3 text-xs text-gray-500 leading-relaxed">{{ implode(', ', $orgRole->permissions ?? []) }}</td>
|
|
<td class="px-4 py-3 text-sm">
|
|
@if ($orgRole->is_system)
|
|
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">{{ __('System') }}</span>
|
|
@else
|
|
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">{{ __('Custom') }}</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-3 text-right">
|
|
<div class="flex justify-end space-x-2">
|
|
@can('update roles')
|
|
<x-jetstream.secondary-button title="{{ __('Edit') }}"
|
|
wire:click="openEditOrgRoleModal({{ $orgRole->id }})">
|
|
<x-icon name="pencil-square" class="h-5 w-5" />
|
|
</x-jetstream.secondary-button>
|
|
@endcan
|
|
@can('delete roles')
|
|
@if (!$orgRole->is_system)
|
|
<x-jetstream.danger-button title="{{ __('Delete') }}"
|
|
wire:click="openDeleteOrgRoleModal({{ $orgRole->id }})">
|
|
<x-icon name="trash" class="h-5 w-5" />
|
|
</x-jetstream.danger-button>
|
|
@endif
|
|
@endcan
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">{{ __('No roles found.') }}</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bank Roles -->
|
|
<div class="mt-12 mb-6">
|
|
<h2 class="text-lg font-semibold text-theme-primary">{{ __('Bank Roles') }}</h2>
|
|
<p class="mt-1 text-sm text-theme-secondary">{{ __('Roles assigned to users linked to bank profiles. System roles cannot be deleted.') }}</p>
|
|
</div>
|
|
|
|
@can('create roles')
|
|
<div class="mb-6">
|
|
<x-jetstream.button wire:click="openCreateOrgRoleModal('bank')" class="bg-theme-brand hover:bg-opacity-80">
|
|
<x-icon name="plus" class="mr-2 h-4 w-4" />
|
|
{{ __('Create Bank Role') }}
|
|
</x-jetstream.button>
|
|
</div>
|
|
@endcan
|
|
|
|
<div class="overflow-hidden rounded-lg bg-white shadow-sm">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Label') }}</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Name') }}</th>
|
|
<th class="hidden xl:table-cell px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Permissions') }}</th>
|
|
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Type') }}</th>
|
|
<th class="px-4 py-3 text-right text-xs font-medium uppercase tracking-wider text-gray-500">{{ __('Actions') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 bg-white">
|
|
@forelse ($bankRoles as $orgRole)
|
|
<tr class="hover:bg-gray-50 align-top" wire:key="bank-role-{{ $orgRole->id }}">
|
|
<td class="px-4 py-3 text-sm text-gray-900">{{ $orgRole->label }}</td>
|
|
<td class="px-4 py-3 text-sm text-gray-700">{{ $orgRole->name }}</td>
|
|
<td class="hidden xl:table-cell px-4 py-3 text-xs text-gray-500 leading-relaxed">{{ implode(', ', $orgRole->permissions ?? []) }}</td>
|
|
<td class="px-4 py-3 text-sm">
|
|
@if ($orgRole->is_system)
|
|
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">{{ __('System') }}</span>
|
|
@else
|
|
<span class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">{{ __('Custom') }}</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-3 text-right">
|
|
<div class="flex justify-end space-x-2">
|
|
@can('update roles')
|
|
<x-jetstream.secondary-button title="{{ __('Edit') }}"
|
|
wire:click="openEditOrgRoleModal({{ $orgRole->id }})">
|
|
<x-icon name="pencil-square" class="h-5 w-5" />
|
|
</x-jetstream.secondary-button>
|
|
@endcan
|
|
@can('delete roles')
|
|
@if (!$orgRole->is_system)
|
|
<x-jetstream.danger-button title="{{ __('Delete') }}"
|
|
wire:click="openDeleteOrgRoleModal({{ $orgRole->id }})">
|
|
<x-icon name="trash" class="h-5 w-5" />
|
|
</x-jetstream.danger-button>
|
|
@endif
|
|
@endcan
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="5" class="px-4 py-8 text-center text-sm text-gray-400">{{ __('No roles found.') }}</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
@php
|
|
// Rows = resources, columns = verbs.
|
|
// Each row lists only the verbs that apply to that resource.
|
|
$permissionGroups = [
|
|
['label' => 'Posts', 'resource' => 'posts', 'verbs' => ['view','create','update','publish','unpublish','delete','manage']],
|
|
['label' => 'Polls', 'resource' => 'polls', 'verbs' => ['view','create','update','export','delete','manage']],
|
|
['label' => 'Calls', 'resource' => 'calls', 'verbs' => ['view','create','update','publish','unpublish','delete','manage']],
|
|
['label' => 'Mailings', 'resource' => 'mailings', 'verbs' => ['view','create','update','send','delete','manage']],
|
|
['label' => 'Tags', 'resource' => 'tags', 'verbs' => ['view','create','update','delete','manage']],
|
|
['label' => 'Categories', 'resource' => 'categories', 'verbs' => ['view','create','update','delete','manage']],
|
|
['label' => 'Users', 'resource' => 'users', 'verbs' => ['view','create','update','delete','manage']],
|
|
['label' => 'Organizations', 'resource' => 'organizations', 'verbs' => ['view','create','update','delete','manage']],
|
|
['label' => 'Banks', 'resource' => 'banks', 'verbs' => ['view','create','update','delete','manage']],
|
|
['label' => 'Admins', 'resource' => 'admins', 'verbs' => ['view','create','update','delete','manage']],
|
|
['label' => 'Accounts', 'resource' => 'accounts', 'verbs' => ['view','create','update','delete','manage']],
|
|
['label' => 'Profiles', 'resource' => 'profiles', 'verbs' => ['view','create','update','delete','manage']],
|
|
['label' => 'Roles', 'resource' => 'roles', 'verbs' => ['view','create','update','delete','manage']],
|
|
['label' => 'Statistics', 'resource' => 'stats', 'verbs' => ['view'], 'unlocked' => true],
|
|
['label' => 'Maintenance', 'resource' => 'maintenance', 'verbs' => ['manage']],
|
|
['section' => 'Logs'],
|
|
['label' => 'Search', 'resource' => 'search-metrics', 'verbs' => ['view'], 'unlocked' => true],
|
|
['label' => 'Search Reindex', 'resource' => 'search-reindex-log', 'verbs' => ['view'], 'unlocked' => true],
|
|
['label' => 'Application', 'resource' => 'application-log', 'verbs' => ['view'], 'unlocked' => true],
|
|
['label' => 'Inactive Profiles', 'resource' => 'inactive-profiles-log', 'verbs' => ['view'], 'unlocked' => true],
|
|
];
|
|
|
|
// All verb columns in display order
|
|
$allVerbs = ['view','create','update','publish','unpublish','send','export','delete','manage'];
|
|
|
|
// Admin profile capability permissions (separate inline grid)
|
|
$adminCapGroups = [
|
|
['inline' => 'Capabilities', 'permissions' => [
|
|
'admin reports' => 'Reports',
|
|
'admin contacts' => 'Contacts',
|
|
'admin messenger' => 'Messenger',
|
|
'admin settings' => 'Settings',
|
|
'admin change-password' => 'Change Password',
|
|
]],
|
|
];
|
|
$adminCapVerbs = ['reports', 'contacts', 'messenger', 'settings', 'change-password'];
|
|
@endphp
|
|
|
|
<!-- Create Role Modal -->
|
|
@if ($modalCreateRole)
|
|
<x-jetstream.dialog-modal wire:key="modalCreateRole" wire:model.live="modalCreateRole" maxWidth="4xl">
|
|
<x-slot name="title">{{ __('Create New Role') }}</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<div class="space-y-5">
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<x-input
|
|
label="{{ __('Name') }}"
|
|
placeholder="{{ __('e.g. site-editor') }}"
|
|
hint="{{ __('Lowercase letters, numbers and hyphens only. Cannot be changed later.') }}"
|
|
wire:model.defer="createRole.name" />
|
|
<x-input
|
|
label="{{ __('Label') }}"
|
|
placeholder="{{ __('e.g. Site Editor') }}"
|
|
hint="{{ __('Human-readable display name.') }}"
|
|
wire:model.defer="createRole.label" />
|
|
</div>
|
|
|
|
<div>
|
|
<label class="mb-2 block text-sm font-medium text-gray-700">{{ __('Permissions') }}</label>
|
|
@include('livewire.roles._permission-grid', ['model' => 'createRole', 'isSystem' => false])
|
|
@error('createRole.permissions')
|
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="mb-1 block text-sm font-medium text-gray-700">{{ __('Admin Profile Capabilities') }}</label>
|
|
<p class="mb-2 text-xs text-gray-500">{{ __('Permissions that apply when the user is acting as an admin profile.') }}</p>
|
|
@include('livewire.roles._permission-grid', ['model' => 'createRole', 'isSystem' => false, 'permissionGroups' => $adminCapGroups, 'allVerbs' => $adminCapVerbs])
|
|
</div>
|
|
</div>
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="$set('modalCreateRole', false)" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.button class="ml-3 bg-theme-brand" wire:click.prevent="saveCreate" wire:loading.attr="disabled">
|
|
{{ __('Create Role') }}
|
|
</x-jetstream.button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!-- Edit Role Modal -->
|
|
@if ($modalEditRole)
|
|
<x-jetstream.dialog-modal wire:key="modalEditRole" wire:model.live="modalEditRole" maxWidth="4xl">
|
|
<x-slot name="title">
|
|
{{ __('Edit Role') }}: <span class="font-mono text-base">{{ $editRole['name'] }}</span>
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
<div class="space-y-5">
|
|
<x-input
|
|
label="{{ __('Label') }}"
|
|
placeholder="{{ __('e.g. Site Editor') }}"
|
|
wire:model.defer="editRole.label" />
|
|
|
|
<div>
|
|
<label class="mb-2 block text-sm font-medium text-gray-700">{{ __('Permissions') }}</label>
|
|
@include('livewire.roles._permission-grid', ['model' => 'editRole', 'isSystem' => $editRoleIsSystem])
|
|
@error('editRole.permissions')
|
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="mb-1 block text-sm font-medium text-gray-700">{{ __('Admin Profile Capabilities') }}</label>
|
|
<p class="mb-2 text-xs text-gray-500">{{ __('Permissions that apply when the user is acting as an admin profile.') }}</p>
|
|
@include('livewire.roles._permission-grid', ['model' => 'editRole', 'isSystem' => $editRoleIsSystem, 'permissionGroups' => $adminCapGroups, 'allVerbs' => $adminCapVerbs])
|
|
</div>
|
|
|
|
<div class="rounded-md border border-gray-300 bg-gray-50 p-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<x-icon class="h-5 w-5 text-gray-400" name="exclamation-triangle" />
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-gray-800">
|
|
<strong>{{ __('Important') }}:</strong>
|
|
{{ __('Changing permissions will immediately update all admin profiles that have this role assigned.') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</x-slot>
|
|
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="$set('modalEditRole', false)" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.button class="ml-3 bg-theme-brand" wire:click.prevent="saveEdit" wire:loading.attr="disabled">
|
|
{{ __('Save Changes') }}
|
|
</x-jetstream.button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!-- Delete Role Modal -->
|
|
@if ($modalDeleteRole)
|
|
<x-jetstream.dialog-modal wire:key="modalDeleteRole" wire:model.live="modalDeleteRole" maxWidth="lg">
|
|
<x-slot name="title">{{ __('Delete Role') }}</x-slot>
|
|
<x-slot name="content">
|
|
<div class="space-y-4">
|
|
<p class="text-sm text-gray-700">
|
|
{{ __('Are you sure you want to delete the role') }}
|
|
<strong>{{ $deleteRoleName }}</strong>?
|
|
</p>
|
|
@if (!empty($deleteAffectedUsers))
|
|
<div class="rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700">
|
|
<p class="mb-2 font-medium flex items-center gap-1">
|
|
<x-icon name="exclamation-triangle" class="h-4 w-4 shrink-0" />
|
|
{{ __('The following users will be unlinked from their admin profile:') }}
|
|
</p>
|
|
<ul class="ml-5 list-disc space-y-0.5">
|
|
@foreach ($deleteAffectedUsers as $entry)
|
|
<li>{{ $entry['user'] }} — <span class="font-medium">{{ $entry['profile'] }}</span></li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@else
|
|
<div class="rounded-md border border-gray-200 bg-gray-50 p-3 text-sm text-gray-600">
|
|
<x-icon name="information-circle" class="mr-1 inline h-4 w-4" />
|
|
{{ __('No users are currently assigned this role.') }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="$set('modalDeleteRole', false)" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.danger-button class="ml-3" wire:click.prevent="deleteRole" wire:loading.attr="disabled">
|
|
{{ __('Delete Role') }}
|
|
</x-jetstream.danger-button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
@php
|
|
$orgPermissionGroups = [
|
|
['inline' => 'Capabilities', 'permissions' => [
|
|
'org pay' => 'Pay',
|
|
'org transactions' => 'Transactions',
|
|
'org reports' => 'Reports',
|
|
'org stats' => 'Statistics',
|
|
'org contacts' => 'Contacts',
|
|
'org calls' => 'Calls',
|
|
'org messenger' => 'Messenger',
|
|
'org edit-profile' => 'Edit Profile',
|
|
'org settings' => 'Settings',
|
|
'org change-password' => 'Change Password',
|
|
]],
|
|
];
|
|
$orgAllVerbs = ['pay','transactions','reports','stats','contacts','calls','messenger','edit-profile','settings','change-password'];
|
|
@endphp
|
|
|
|
<!-- Create Org/Bank Role Modal -->
|
|
@if ($modalCreateOrgRole)
|
|
<x-jetstream.dialog-modal wire:key="modalCreateOrgRole" wire:model.live="modalCreateOrgRole" maxWidth="4xl">
|
|
<x-slot name="title">{{ $createOrgRoleType === 'bank' ? __('Create New Bank Role') : __('Create New Organization Role') }}</x-slot>
|
|
<x-slot name="content">
|
|
<div class="space-y-5">
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<x-input
|
|
label="{{ __('Name') }}"
|
|
placeholder="{{ $createOrgRoleType === 'bank' ? __('e.g. bank-editor') : __('e.g. organization-editor') }}"
|
|
hint="{{ __('Must start with') }} "{{ $createOrgRoleType }}-". {{ __('Lowercase letters, numbers and hyphens only. Cannot be changed later.') }}"
|
|
wire:model.defer="createOrgRole.name" />
|
|
<x-input
|
|
label="{{ __('Label') }}"
|
|
placeholder="{{ __('e.g. Org Editor') }}"
|
|
hint="{{ __('Human-readable display name.') }}"
|
|
wire:model.defer="createOrgRole.label" />
|
|
</div>
|
|
<div>
|
|
<label class="mb-2 block text-sm font-medium text-gray-700">{{ __('Permissions') }}</label>
|
|
@include('livewire.roles._permission-grid', ['model' => 'createOrgRole', 'isSystem' => false, 'permissionGroups' => $orgPermissionGroups, 'allVerbs' => $orgAllVerbs])
|
|
@error('createOrgRole.permissions')
|
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
</div>
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="$set('modalCreateOrgRole', false)" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.button class="ml-3 bg-theme-brand" wire:click.prevent="saveCreateOrgRole" wire:loading.attr="disabled">
|
|
{{ __('Create Role') }}
|
|
</x-jetstream.button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!-- Edit Org Role Modal -->
|
|
@if ($modalEditOrgRole)
|
|
<x-jetstream.dialog-modal wire:key="modalEditOrgRole" wire:model.live="modalEditOrgRole" maxWidth="4xl">
|
|
<x-slot name="title">
|
|
{{ __('Edit Role') }}: <span class="font-mono text-base">{{ $editOrgRole['name'] }}</span>
|
|
</x-slot>
|
|
<x-slot name="content">
|
|
<div class="space-y-5">
|
|
<x-input
|
|
label="{{ __('Label') }}"
|
|
placeholder="{{ __('e.g. Org Editor') }}"
|
|
wire:model.defer="editOrgRole.label" />
|
|
<div>
|
|
<label class="mb-2 block text-sm font-medium text-gray-700">{{ __('Permissions') }}</label>
|
|
@include('livewire.roles._permission-grid', ['model' => 'editOrgRole', 'isSystem' => $editOrgRoleIsSystem, 'permissionGroups' => $orgPermissionGroups, 'allVerbs' => $orgAllVerbs])
|
|
@error('editOrgRole.permissions')
|
|
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
<div class="rounded-md border border-gray-300 bg-gray-50 p-4">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<x-icon class="h-5 w-5 text-gray-400" name="exclamation-triangle" />
|
|
</div>
|
|
<div class="ml-3">
|
|
<p class="text-sm text-gray-800">
|
|
<strong>{{ __('Important') }}:</strong>
|
|
{{ str_starts_with($editOrgRole['name'], 'bank-') ? __('Changing permissions will immediately update all bank profiles that have this role assigned.') : __('Changing permissions will immediately update all organization profiles that have this role assigned.') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="$set('modalEditOrgRole', false)" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.button class="ml-3 bg-theme-brand" wire:click.prevent="saveEditOrgRole" wire:loading.attr="disabled">
|
|
{{ __('Save Changes') }}
|
|
</x-jetstream.button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
<!-- Delete Org Role Modal -->
|
|
@if ($modalDeleteOrgRole)
|
|
<x-jetstream.dialog-modal wire:key="modalDeleteOrgRole" wire:model.live="modalDeleteOrgRole" maxWidth="lg">
|
|
<x-slot name="title">{{ __('Delete Role') }}</x-slot>
|
|
<x-slot name="content">
|
|
<div class="space-y-4">
|
|
<p class="text-sm text-gray-700">
|
|
{{ __('Are you sure you want to delete the role') }}
|
|
<strong>{{ $deleteOrgRoleName }}</strong>?
|
|
</p>
|
|
@if (!empty($deleteOrgAffectedUsers))
|
|
<div class="rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-700">
|
|
<p class="mb-2 font-medium flex items-center gap-1">
|
|
<x-icon name="exclamation-triangle" class="h-4 w-4 shrink-0" />
|
|
{{ $deleteOrgRoleType === 'bank'
|
|
? __('The following users will be unlinked from their bank profile:')
|
|
: __('The following users will be unlinked from their organization profile:') }}
|
|
</p>
|
|
<ul class="ml-5 list-disc space-y-0.5">
|
|
@foreach ($deleteOrgAffectedUsers as $entry)
|
|
<li>{{ $entry['user'] }} — <span class="font-medium">{{ $entry['profile'] }}</span></li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@else
|
|
<div class="rounded-md border border-gray-200 bg-gray-50 p-3 text-sm text-gray-600">
|
|
<x-icon name="information-circle" class="mr-1 inline h-4 w-4" />
|
|
{{ __('No users are currently assigned this role.') }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="$set('modalDeleteOrgRole', false)" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.danger-button class="ml-3" wire:click.prevent="deleteOrgRole" wire:loading.attr="disabled">
|
|
{{ __('Delete Role') }}
|
|
</x-jetstream.danger-button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
</div>
|