feature: add role permission table for HR Manager
- Add TableHeader DTO - Refactor Toggle component to have checked prop, as wire:model does not work - add null collasing for php 8.5 strict compatibility in scope directive - refactor card component so that actions stay on the right - make button component scaled small on active and change color on hover
This commit is contained in:
parent
ede0064b54
commit
ca628fb009
18
app/Data/Ui/ManageRoles/PermissionData.php
Normal file
18
app/Data/Ui/ManageRoles/PermissionData.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Data\Ui\ManageRoles;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
final class PermissionData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $name,
|
||||||
|
public bool $view,
|
||||||
|
public bool $create,
|
||||||
|
public bool $update,
|
||||||
|
public bool $delete,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
16
app/Data/Ui/TableHeader.php
Normal file
16
app/Data/Ui/TableHeader.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Data\Ui;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
final class TableHeader extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $key,
|
||||||
|
public string $label,
|
||||||
|
public ?array $format = null,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ public function boot(): void
|
|||||||
{
|
{
|
||||||
$this->registerScopeDirective();
|
$this->registerScopeDirective();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerScopeDirective(): void
|
public function registerScopeDirective(): void
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -30,7 +31,10 @@ public function registerScopeDirective(): void
|
|||||||
$directiveArguments = preg_split("/,(?![^\(\(]*[\)\)])/", $expression);
|
$directiveArguments = preg_split("/,(?![^\(\(]*[\)\)])/", $expression);
|
||||||
$directiveArguments = array_map("trim", $directiveArguments);
|
$directiveArguments = array_map("trim", $directiveArguments);
|
||||||
|
|
||||||
[$name, $functionArguments] = $directiveArguments;
|
// PHP 8.5 Safe Unpacking (Prevents "Undefined array key" crashes)
|
||||||
|
$name = $directiveArguments[0] ?? "''";
|
||||||
|
$functionArguments = $directiveArguments[1] ?? '';
|
||||||
|
|
||||||
// Build function "uses" to inject extra external variables
|
// Build function "uses" to inject extra external variables
|
||||||
$uses = Arr::except(array_flip($directiveArguments), [$name, $functionArguments]);
|
$uses = Arr::except(array_flip($directiveArguments), [$name, $functionArguments]);
|
||||||
$uses = array_flip($uses);
|
$uses = array_flip($uses);
|
||||||
@ -39,13 +43,14 @@ public function registerScopeDirective(): void
|
|||||||
$uses = implode(",", $uses);
|
$uses = implode(",", $uses);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slot names can`t contains dot , eg: `user.city`.
|
* Slot names can`t contains dot , eg: `user.city`.
|
||||||
* So we convert `user.city` to `user___city`
|
* So we convert `user.city` to `user___city`
|
||||||
*
|
*
|
||||||
* Later, on component it will be replaced back.
|
* Later, on component it will be replaced back.
|
||||||
*/
|
*/
|
||||||
$name = str_replace(".", "___", $name);
|
$name = str_replace(".", "___", $name);
|
||||||
|
|
||||||
|
// PHP 8.5 Safe Loop Check
|
||||||
return "<?php \$__bladeCompiler = \$__bladeCompiler ?? null; \$__env->slot({$name}, function({$functionArguments}) use ({$uses}) { \$loop = !empty(\$__env->getLoopStack()) ? (object) \$__env->getLoopStack()[0] : null; ?>";
|
return "<?php \$__bladeCompiler = \$__bladeCompiler ?? null; \$__env->slot({$name}, function({$functionArguments}) use ({$uses}) { \$loop = !empty(\$__env->getLoopStack()) ? (object) \$__env->getLoopStack()[0] : null; ?>";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -57,10 +57,10 @@
|
|||||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
|
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
|
||||||
],
|
],
|
||||||
"lint": [
|
"lint": [
|
||||||
"pint --parallel"
|
"pint --parallel --dirty"
|
||||||
],
|
],
|
||||||
"lint:check": [
|
"lint:check": [
|
||||||
"pint --parallel --test"
|
"pint --parallel --dirty --test"
|
||||||
],
|
],
|
||||||
"ci:check": [
|
"ci:check": [
|
||||||
"Composer\\Config::disableProcessTimeout",
|
"Composer\\Config::disableProcessTimeout",
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Data\Ui\ManageRoles\PermissionData;
|
||||||
|
use App\Data\Ui\TableHeader;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||||
|
use Spatie\LaravelData\DataCollection;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
#[DataCollectionOf(TableHeader::class)]
|
||||||
|
public DataCollection $header;
|
||||||
|
|
||||||
|
#[DataCollectionOf(PermissionData::class)]
|
||||||
|
public DataCollection $rolePermissions;
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->header = TableHeader::collect([
|
||||||
|
new TableHeader('name', 'Permission'),
|
||||||
|
new TableHeader('view', 'View'),
|
||||||
|
new TableHeader('create', 'Create'),
|
||||||
|
new TableHeader('update', 'Edit'),
|
||||||
|
new TableHeader('delete', 'Delete'),
|
||||||
|
], DataCollection::class);
|
||||||
|
|
||||||
|
$this->rolePermissions = PermissionData::collect([
|
||||||
|
new PermissionData('Outlook - emails', true, true, false, false),
|
||||||
|
new PermissionData('Teams - Messages', true, false, true, true),
|
||||||
|
], DataCollection::class);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-shared.card title="Role Permission - HR Manager" icon="lucide-lock" class="p-0">
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-shared.button icon="lucide-x">
|
||||||
|
{{ __('Revoke All') }}
|
||||||
|
</x-shared.button>
|
||||||
|
<x-shared.button icon="lucide-check">
|
||||||
|
{{ __('Save') }}
|
||||||
|
</x-shared.button>
|
||||||
|
</x-slot:actions>
|
||||||
|
|
||||||
|
<x-table :headers="$header->toArray()" :rows="$rolePermissions">
|
||||||
|
@scope('cell_view', $row)
|
||||||
|
<x-shared.toggle :checked="$row->view" />
|
||||||
|
@endscope
|
||||||
|
@scope('cell_create', $row)
|
||||||
|
<x-shared.toggle :checked="$row->create" />
|
||||||
|
@endscope
|
||||||
|
@scope('cell_update', $row)
|
||||||
|
<x-shared.toggle :checked="$row->update" />
|
||||||
|
@endscope
|
||||||
|
@scope('cell_delete', $row)
|
||||||
|
<x-shared.toggle :checked="$row->update" />
|
||||||
|
@endscope
|
||||||
|
</x-table>
|
||||||
|
</x-shared.card>
|
||||||
|
</div>
|
||||||
@ -1,34 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Data\Ui\ManageRoles\RoleData;
|
use App\Data\Ui\ManageRoles\RoleData;
|
||||||
use App\Data\Ui\ManageRoles\RolesCollection;
|
|
||||||
use App\Data\Ui\ManageRoles\RolesData;
|
|
||||||
use App\Data\Ui\ManageRoles\ServiceCollection;
|
|
||||||
use App\Data\Ui\ManageRoles\ServiceData;
|
use App\Data\Ui\ManageRoles\ServiceData;
|
||||||
use App\Data\Users\UserCollection;
|
use App\Data\Ui\TableHeader;
|
||||||
use App\Data\Users\UserData;
|
use App\Data\Users\UserData;
|
||||||
use App\Data\Users\UsersData;
|
|
||||||
use App\Enums\RolesStatus;
|
use App\Enums\RolesStatus;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||||
use Spatie\LaravelData\DataCollection;
|
use Spatie\LaravelData\DataCollection;
|
||||||
|
|
||||||
new class extends Component {
|
new class extends Component {
|
||||||
/**
|
#[DataCollectionOf(TableHeader::class)]
|
||||||
* @var array<int, array{
|
public DataCollection $header;
|
||||||
* key: string,
|
|
||||||
* label: string,
|
|
||||||
* format: array{0: string, 1: string}|null
|
|
||||||
* }>
|
|
||||||
*/
|
|
||||||
public array $header;
|
|
||||||
|
|
||||||
#[DataCollectionOf(class: RoleData::class)]
|
#[DataCollectionOf(class: RoleData::class)]
|
||||||
public DataCollection $roles;
|
public DataCollection $roles;
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->header = [
|
$this->header = TableHeader::collect([
|
||||||
[
|
[
|
||||||
"key" => "name",
|
"key" => "name",
|
||||||
"label" => "Role Name",
|
"label" => "Role Name",
|
||||||
@ -45,7 +35,7 @@ public function mount(): void
|
|||||||
"key" => "status",
|
"key" => "status",
|
||||||
"label" => "Status",
|
"label" => "Status",
|
||||||
],
|
],
|
||||||
];
|
], DataCollection::class);
|
||||||
$this->roles = RoleData::collect(
|
$this->roles = RoleData::collect(
|
||||||
[
|
[
|
||||||
new RoleData(
|
new RoleData(
|
||||||
@ -91,7 +81,7 @@ public function mount(): void
|
|||||||
</x-shared.button>
|
</x-shared.button>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
|
|
||||||
<x-table :headers="$header" :rows="$roles">
|
<x-table :headers="$header->toArray()" :rows="$roles">
|
||||||
@scope('cell_users', $role)
|
@scope('cell_users', $role)
|
||||||
@php /** @var RoleData $role */ @endphp
|
@php /** @var RoleData $role */ @endphp
|
||||||
<div class="flex -space-x-3">
|
<div class="flex -space-x-3">
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
@props(['icon' => null])
|
@props(['icon' => null])
|
||||||
<button {{$attributes->twMerge(['class' => 'rounded-lg border border-gray-300 px-4 py-2 text-gray-700'])}}>
|
<button {{$attributes->twMerge(['class' => 'rounded-lg border border-gray-300 px-4 py-2 text-gray-700 active:scale-75 hover:bg-gray-100 transition duration-150 ease-in-out'])}}>
|
||||||
<div class="gap-2 flex items-center">
|
<div class="gap-2 flex items-center">
|
||||||
@if($icon)
|
@if($icon)
|
||||||
<span class="block">
|
<span class="block">
|
||||||
|
|||||||
@ -1,19 +1,19 @@
|
|||||||
@props(['title', 'icon', 'actions'])
|
@props(['title', 'icon', 'actions'])
|
||||||
<div {{$attributes->twMerge('rounded-xl border border-gray-200 p-4 shadow-xs bg-white')}}>
|
<div {{$attributes->twMerge('rounded-xl border border-gray-200 p-4 shadow-xs bg-white')}}>
|
||||||
@isset($title)
|
@isset($title)
|
||||||
<div class="p-4 flex justify-between border-b border-gray-200">
|
<div class="p-4 flex justify-between border-b border-gray-200">
|
||||||
<h2 class="font-medium flex items-center gap-x-2">
|
<h2 class="font-medium flex items-center gap-x-2">
|
||||||
@isset($icon)
|
@isset($icon)
|
||||||
<span class="w-5 h-5">
|
<span class="w-5 h-5">@svg($icon)</span>
|
||||||
@svg($icon)
|
@endisset
|
||||||
</span>
|
{{ __($title) }}
|
||||||
@endisset
|
</h2>
|
||||||
{{ __($title) }}
|
<div class="">
|
||||||
</h2>
|
@isset($actions)
|
||||||
@isset($actions)
|
{{$actions}}
|
||||||
{{$actions}}
|
@endisset
|
||||||
@endisset
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endisset
|
@endisset
|
||||||
{{$slot}}
|
{{$slot}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
@props(['disabled' => false, 'label' => null])
|
@props(['disabled' => false, 'label' => null, 'checked' => false])
|
||||||
|
|
||||||
<label class="relative inline-flex items-center cursor-pointer">
|
<label class="relative inline-flex items-center cursor-pointer">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
@checked($checked)
|
||||||
{{ $disabled ? 'disabled' : '' }}
|
{{ $disabled ? 'disabled' : '' }}
|
||||||
{!! $attributes->merge(['class' => 'sr-only peer']) !!}
|
{{ $attributes->merge(['class' => 'sr-only peer']) }}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
|
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
|
||||||
|
|||||||
@ -17,5 +17,6 @@ class extends Component {
|
|||||||
<x-dashboard-stats />
|
<x-dashboard-stats />
|
||||||
<livewire:dashboard.connected-services />
|
<livewire:dashboard.connected-services />
|
||||||
<livewire:dashboard.roles.manager />
|
<livewire:dashboard.roles.manager />
|
||||||
|
<livewire:dashboard.roles.hr-permission />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user