Testing Alpha

This commit is contained in:
2025-05-11 14:14:50 -06:00
parent 988b86a33d
commit a7002701f5
1903 changed files with 77534 additions and 36485 deletions

View File

@ -0,0 +1,27 @@
@props([
'label' => '',
'mode' => 'create',
])
@php
$btnLabel = match($mode) {
'create' => 'Crear ' . $label,
'edit' => 'Guardar cambios',
'delete' => 'Eliminar'
};
$btnType = match($mode) {
'create' => 'success',
'edit' => 'warning',
'delete' => 'danger'
};
@endphp
<div class="pt-3">
<button type="submit" class="btn btn-{{ $btnType }} btn-submit waves-effect waves mr-3 mb-3">{{ $btnLabel }}</button>
<button type="reset" class="btn btn-reset mr-3 mb-3">Cancelar</button>
</div>
@if($mode == 'delete')
<x-vuexy-admin::form.checkbox model="confirmDeletion" label="Confirmar eliminación" parentClass="confirm-deletion" data-always-enabled switch switch-type="square" color="danger" size="lg" with-icon />
@endif

View File

@ -0,0 +1,111 @@
<div class="p-6 space-y-6">
<div class="text-xl font-semibold text-gray-800">
🧪 Diagnóstico del modelo <code>{{ get_class($model) }}</code>
</div>
@php
$debug = method_exists($model, 'getExtendableDebugData') ? $model->getExtendableDebugData() : [];
$attributes = $debug['attributes'] ?? [];
$traits = $debug['traits'] ?? [];
$methods = [];
$methodTypes = ['fillable', 'cast', 'audit', 'hidden', 'appends'];
foreach ($traits as $trait) {
$traitName = class_basename($trait);
foreach ($methodTypes as $type) {
$method = 'get' . ucfirst($type) . $traitName;
if (method_exists($model, $method)) {
try {
$methods[$traitName][$type] = $model->{$method}();
} catch (\Throwable $e) {
$methods[$traitName][$type] = ['⚠️ Error: ' . $e->getMessage()];
}
}
}
}
$simpleProps = [
'hidden' => method_exists($model, 'getHidden') ? $model->getHidden() : ($model->hidden ?? []),
'appends' => method_exists($model, 'getAppends') ? $model->getAppends() : ($model->appends ?? []),
];
@endphp
{{-- GRUPO: Atributos Extendidos --}}
<div class="bg-white border rounded-xl shadow-sm">
<div class="px-5 py-4 border-b bg-gradient-to-r from-gray-100 to-gray-50 rounded-t-xl">
<h2 class="text-lg font-semibold text-gray-700">📦 Atributos extendidos del modelo</h2>
</div>
<div class="p-5 space-y-6">
@foreach ($attributes as $type => $data)
<div>
<h3 class="text-md font-semibold text-gray-800 uppercase tracking-wide mb-2">
{{ $type }}
<span class="text-xs text-gray-500">({{ $data['cache_key'] ?? 'sin cache' }})</span>
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm text-gray-600">
<div>
<p class="font-semibold text-gray-700 mb-1">Base</p>
<pre class="bg-gray-100 p-2 rounded text-xs">@json($data['base'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)</pre>
</div>
<div>
<p class="font-semibold text-gray-700 mb-1">Extendidos</p>
<pre class="bg-gray-100 p-2 rounded text-xs">@json($data['extended'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)</pre>
</div>
<div>
<p class="font-semibold text-gray-700 mb-1">Combinado</p>
<pre class="bg-gray-100 p-2 rounded text-xs">@json($data['combined'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)</pre>
</div>
</div>
<div class="flex flex-wrap items-center gap-4 mt-3 text-xs text-gray-500">
<span>🚀 Cache: <strong class="{{ $data['cache_enabled'] ? 'text-green-600' : 'text-red-600' }}">{{ $data['cache_enabled'] ? 'ACTIVO' : 'INACTIVO' }}</strong></span>
<span>🧪 Bypass: <strong class="{{ $data['bypass_cache'] ? 'text-yellow-600' : 'text-gray-600' }}">{{ $data['bypass_cache'] ? 'Sí (modo local)' : 'No' }}</strong></span>
<span> TTL: {{ $data['ttl'] }} seg</span>
</div>
</div>
@endforeach
</div>
</div>
{{-- GRUPO: hidden y appends --}}
<div class="bg-white border rounded-xl shadow-sm">
<div class="px-5 py-4 border-b bg-gradient-to-r from-gray-100 to-gray-50 rounded-t-xl">
<h2 class="text-lg font-semibold text-gray-700">🔐 Propiedades visibles y ocultas</h2>
</div>
<div class="p-5 grid grid-cols-1 md:grid-cols-2 gap-6 text-sm text-gray-700">
@foreach($simpleProps as $key => $value)
<div>
<h3 class="font-semibold mb-2 capitalize">{{ $key }}</h3>
<pre class="bg-gray-100 p-2 rounded text-xs">@json($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)</pre>
</div>
@endforeach
</div>
</div>
{{-- GRUPO: Traits --}}
<div class="bg-white border rounded-xl shadow-sm">
<div class="px-5 py-4 border-b bg-gradient-to-r from-gray-100 to-gray-50 rounded-t-xl">
<h2 class="text-lg font-semibold text-gray-700">🧠 Traits detectados</h2>
</div>
<div class="p-5">
<ul class="text-sm text-gray-700 list-disc pl-5 space-y-1">
@foreach($traits as $trait)
<li><code class="text-indigo-600">{{ $trait }}</code></li>
@endforeach
</ul>
</div>
</div>
{{-- GRUPO: Métodos por Trait --}}
<div class="bg-white border rounded-xl shadow-sm">
<div class="px-5 py-4 border-b bg-gradient-to-r from-gray-100 to-gray-50 rounded-t-xl">
<h2 class="text-lg font-semibold text-gray-700">🔍 Métodos de extensión encontrados</h2>
</div>
<div class="p-5">
<pre class="bg-gray-100 p-3 rounded text-xs text-gray-700">@json($methods, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)</pre>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
@props([
'id' => uniqid(), // ID único del formulario
'uniqueId' => '', // ID único del formulario
'uid' => '', // ID único del formulario
'mode' => 'create', // Modo actual ('create', 'edit', 'delete')
'method' => 'POST', // Método del formulario (POST, GET, PUT, DELETE)
'action' => '', // URL de acción
@ -31,10 +31,10 @@
<form {{ $attributes->merge($formAttributes) }}>
@if (!$whitOutId)
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="id" />
<x-vuexy-admin::form.input :uid="$uid" type="hidden" model="id" />
@endif
@if (!$whitOutMode)
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="mode" />
<x-vuexy-admin::form.input :uid="$uid" type="hidden" model="mode" />
@endif
@if ($mode !== 'delete' && in_array($actionPosition, ['top', 'both']))
<div class="notification-container mb-4"></div>

View File

@ -0,0 +1,81 @@
@props([
'uid' => uniqid(),
'model' => '',
'label' => 'Contraseña',
'placeholder' => '············',
'labelClass' => '',
'icon' => null,
])
@php
$livewireModel = $attributes->get('wire:model', $model);
$name = $attributes->get('name', $livewireModel);
$inputId = $attributes->get('id', $name . '_' . $uid);
$errorKey = $livewireModel ?: $name;
$hasError = $errors->has($errorKey);
$errorClass = $hasError ? 'is-invalid' : '';
@endphp
<div class="mb-4 fv-row">
<label class="form-label {{ $labelClass }}" for="{{ $inputId }}">{{ $label }}</label>
<div class="input-group input-group-merge">
{{-- Prefijo (opcional) --}}
@if ($icon)
<span class="input-group-text">
<i class="{{ $icon }}"></i>
</span>
@endif
{{-- Input --}}
<input
type="password"
id="{{ $inputId }}"
name="{{ $name }}"
wire:model="{{ $livewireModel }}"
class="form-control {{ $errorClass }}"
placeholder="{{ $placeholder }}"
autocomplete="new-password"
aria-describedby="{{ $inputId }}-aria"
/>
{{-- Toggle --}}
<span class="input-group-text cursor-pointer toggle-password" data-target="{{ $inputId }}">
<i class="ti ti-eye-off password-toggle-icon"></i>
</span>
</div>
@if ($hasError)
<span class="text-danger">{{ $errors->first($errorKey) }}</span>
@endif
</div>
@push('page-script')
<script>
function attachPasswordToggles() {
document.querySelectorAll('.toggle-password').forEach(toggle => {
const alreadyAttached = toggle.getAttribute('data-bound');
if (alreadyAttached) return;
toggle.setAttribute('data-bound', 'true');
toggle.addEventListener('click', function () {
const targetId = this.getAttribute('data-target');
const input = document.getElementById(targetId);
const icon = this.querySelector('.password-toggle-icon');
if (input && icon) {
const isPassword = input.type === 'password';
input.type = isPassword ? 'text' : 'password';
icon.classList.toggle('ti-eye', isPassword);
icon.classList.toggle('ti-eye-off', !isPassword);
}
});
});
}
document.addEventListener("DOMContentLoaded", function () {
attachPasswordToggles();
});
</script>
@endpush

View File

@ -146,14 +146,14 @@
{{-- Prefijo --}}
@if ($prefix || $prefixIcon)
@if ($prefixClickable)
<button type="button" class="input-group-text cursor-pointer" {{ $prefixAction ? "wire:click=$prefixAction" : '' }}>
<span class="input-group-text cursor-pointer" {{ $prefixAction ? "wire:click=$prefixAction" : '' }}>
@if ($prefixIcon)
<i class="{{ $prefixIcon }}"></i>
@endif
@if ($prefix)
{{ $prefix }}
@endif
</button>
</span>
@else
<span class="input-group-text">
@if ($prefixIcon)
@ -171,14 +171,14 @@
{{-- Sufijo --}}
@if ($suffix || $suffixIcon)
@if ($suffixClickable)
<button type="button" class="input-group-text cursor-pointer" {{ $suffixAction ? "wire:click=$suffixAction" : '' }}>
<span class="input-group-text cursor-pointer" {{ $suffixAction ? "wire:click=$suffixAction" : '' }}>
@if ($suffixIcon)
<i class="{{ $suffixIcon }}"></i>
@endif
@if ($suffix)
{{ $suffix }}
@endif
</button>
</span>
@else
<span class="input-group-text">
@if ($suffixIcon)

View File

@ -6,13 +6,13 @@
'id' => uniqid(),
'tagName' => '',
'datatableConfig' => [],
'routes' => [],
'noFilterButtons' => false
])
@php
if($tagName)
if($tagName){
$id = 'bt-' . Str::kebab($tagName) . 's';
}
@endphp
<div id="{{ $id }}" wire:ignore>
@ -58,9 +58,9 @@
});
});
</script>
@isset($routes)
@isset($datatableConfig['routes'])
<script id="app-routes" type="application/json">
{!! json_encode($routes) !!}
{!! json_encode($datatableConfig['routes']) !!}
</script>
@endisset
@endpush

View File

@ -0,0 +1,48 @@
<!-- resources/views/components/user/details/panel.blade.php -->
@props([
'user' => null,
'tabs' => [],
'stats' => [],
'timeline' => [],
'connections' => [],
'teams' => [],
])
<div class="container-xxl flex-grow-1 container-p-y">
<!-- Banner y Header -->
<x-vuexy-admin::user.details.banner-profile-header
:banner-url="$user?->banner_url"
:avatar-url="$user?->profile_photo_url"
:name="$user?->name"
:position="$user?->position"
:location="$user?->location"
:joined="$user?->joined_at?->format('F Y')"
button-label="Connected"
button-icon="ti ti-user-check"
/>
<!-- Tabs -->
<x-vuexy-admin::user.details.tabs :tabs="$tabs" />
<!-- Contenido Principal -->
<div class="row">
<!-- Columna Izquierda -->
<div class="col-xl-4 col-lg-5 col-md-5">
<x-vuexy-admin::user.details.about :user="$user" class="mb-6" />
<x-vuexy-admin::user.details.overview :stats="$stats" />
</div>
<!-- Columna Derecha -->
<div class="col-xl-8 col-lg-7 col-md-7">
<x-vuexy-admin::user.details.timeline :items="$timeline" class="mb-6" />
<div class="row">
<div class="col-lg-12 col-xl-6">
<x-vuexy-admin::user.details.connections :connections="$connections" />
</div>
<div class="col-lg-12 col-xl-6">
<x-vuexy-admin::user.details.teams :teams="$teams" />
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,111 @@
@props(['user'])
@php
$locale = app()->getLocale();
$roles = $user->roles ?? collect();
$flags = $user->getRegisteredFlags() ?? [];
@endphp
<div class="card mb-4">
<div class="card-body">
{{-- SECCIÓN: DATOS PERSONALES --}}
<small class="text-uppercase text-muted">Información</small>
<ul class="list-unstyled my-3 py-1">
<li class="d-flex align-items-center mb-3">
<i class="ti ti-user ti-lg me-2 text-muted"></i>
<span class="fw-medium me-1">Nombre:</span>
<span>{{ $user->full_name ?? 'N/A' }}</span>
</li>
<li class="d-flex align-items-center mb-3">
<i class="ti ti-check ti-lg me-2 text-muted"></i>
<span class="fw-medium me-1">Estado:</span>
<span>{{ $user->status_label ?? 'Sin estado' }}</span>
</li>
<li class="d-flex align-items-start mb-3">
<i class="ti ti-shield ti-lg me-2 text-muted"></i>
<div>
<small class="card-text text-uppercase text-muted small">Roles</small>
<ul class="list-unstyled my-3 py-1 d-flex flex-wrap gap-2">
@foreach($user->roles as $role)
@php
$meta = json_decode($role->ui_metadata, true) ?? [];
$icon = $meta['icon'] ?? 'ti ti-user';
$style = $meta['style'] ?? 'primary'; // default badge style
$description = $meta['description'][app()->getLocale()] ?? $role->name;
@endphp
<li>
<span class="badge bg-label-{{ $style }} d-inline-flex align-items-center gap-1"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ $description }}">
<i class="{{ $icon }} mr-1"></i> {{ $role->name }}
</span>
</li>
@endforeach
</ul>
</div>
</li>
@if(!empty($flags))
<li class="d-flex align-items-start mb-3">
<i class="ti ti-flag ti-lg me-2 text-muted"></i>
<div>
<div class="fw-medium">Flags:</div>
<span class="text-muted">{{ implode(', ', array_keys($flags)) }}</span>
</div>
</li>
@endif
</ul>
{{-- SECCIÓN: CONTACTO --}}
<small class="text-uppercase text-muted">Contacto</small>
<ul class="list-unstyled my-3 py-1">
<li class="d-flex align-items-center mb-3">
<i class="ti ti-mail ti-lg me-2 text-muted"></i>
<span class="fw-medium me-1">Email:</span>
<span>{{ $user->email }}</span>
</li>
<li class="d-flex align-items-center mb-3">
<i class="ti ti-phone ti-lg me-2 text-muted"></i>
<span class="fw-medium me-1">Teléfono:</span>
<span>{{ $user->phone ?? 'No registrado' }}</span>
</li>
<li class="d-flex align-items-center mb-3">
<i class="ti ti-brand-skype ti-lg me-2 text-muted"></i>
<span class="fw-medium me-1">Skype:</span>
<span>{{ $user->skype ?? 'No registrado' }}</span>
</li>
</ul>
{{-- SECCIÓN: OTROS --}}
<small class="text-uppercase text-muted">Otros</small>
<ul class="list-unstyled mt-3 pt-1">
<li class="d-flex align-items-center mb-3">
<i class="ti ti-world ti-lg me-2 text-muted"></i>
<span class="fw-medium me-1">País:</span>
<span>{{ $user->country ?? 'No disponible' }}</span>
</li>
<li class="d-flex align-items-center mb-3">
<i class="ti ti-language ti-lg me-2 text-muted"></i>
<span class="fw-medium me-1">Idioma(s):</span>
<span>{{ $user->languages ?? 'Español' }}</span>
</li>
</ul>
{{-- SECCIÓN: EQUIPOS --}}
@if(!empty($user->teams))
<small class="text-uppercase text-muted">Equipos</small>
<ul class="list-unstyled mt-3 pt-1">
@forelse($user->teams as $team)
<li class="d-flex align-items-center mb-2">
<i class="ti ti-users me-2 text-muted"></i>
<span class="fw-medium me-1">{{ $team['name'] }}</span>
<span class="text-muted">({{ $team['members'] }} miembros)</span>
</li>
@empty
<li class="text-muted">Sin equipos asignados</li>
@endforelse
</ul>
@endif
</div>
</div>

View File

@ -0,0 +1,117 @@
@props([
'bannerUrl' => null,
'avatarUrl' => null,
'name' => 'Nombre no definido',
'position' => null,
'location' => null,
'joined' => null,
'buttonLabel' => null,
'buttonIcon' => null,
'buttonClass' => 'btn btn-primary mb-1 waves-effect waves-light',
'buttonAction' => 'javascript:void(0)'
])
<div class="card mb-6">
{{-- Banner superior --}}
<div class="user-profile-header-banner">
@if ($bannerUrl)
<img src="{{ $bannerUrl }}" alt="Banner image" class="rounded-top">
@else
<div class="bg-secondary rounded-top" style="height: 200px;"></div>
@endif
</div>
{{-- Contenido principal del perfil --}}
<div class="user-profile-header d-flex flex-column flex-lg-row text-sm-start text-center mb-5">
<div class="flex-shrink-0 mt-n2 mx-sm-0 mx-auto">
<img src="{{ $avatarUrl }}" alt="user image" class="d-block h-auto ms-0 ms-sm-6 rounded user-profile-img">
</div>
<div class="flex-grow-1 mt-3 mt-lg-5">
<div class="d-flex align-items-md-end align-items-sm-start align-items-center justify-content-md-between justify-content-start mx-5 flex-md-row flex-column gap-4">
<div class="user-profile-info">
<h4 class="mb-2 mt-lg-6">{{ $name }}</h4>
<ul class="list-inline mb-0 d-flex align-items-center flex-wrap justify-content-sm-start justify-content-center gap-4 my-2">
@if ($position)
<li class="list-inline-item d-flex gap-2 align-items-center">
<i class="ti ti-palette ti-lg"></i><span class="fw-medium">{{ $position }}</span>
</li>
@endif
@if ($location)
<li class="list-inline-item d-flex gap-2 align-items-center">
<i class="ti ti-map-pin ti-lg"></i><span class="fw-medium">{{ $location }}</span>
</li>
@endif
@if ($joined)
<li class="list-inline-item d-flex gap-2 align-items-center">
<i class="ti ti-calendar ti-lg"></i><span class="fw-medium">Joined {{ $joined }}</span>
</li>
@endif
</ul>
</div>
@if ($buttonLabel)
<a href="{{ $buttonAction }}" class="{{ $buttonClass }}">
@if ($buttonIcon)
<i class="{{ $buttonIcon }} ti-xs me-2"></i>
@endif
{{ $buttonLabel }}
</a>
@endif
</div>
</div>
</div>
</div>
<!--
<div class="card mb-6">
<div class="user-profile-header-banner">
<img src="http://vuexy-vite.test/assets/img/pages/profile-banner.png" alt="Banner image" class="rounded-top">
</div>
<div class="user-profile-header d-flex flex-column flex-lg-row text-sm-start text-center mb-5">
<div class="flex-shrink-0 mt-n2 mx-sm-0 mx-auto">
<img src="http://vuexy-vite.test/assets/img/avatars/1.png" alt="user image" class="d-block h-auto ms-0 ms-sm-6 rounded user-profile-img">
</div>
<div class="flex-grow-1 mt-3 mt-lg-5">
<div class="d-flex align-items-md-end align-items-sm-start align-items-center justify-content-md-between justify-content-start mx-5 flex-md-row flex-column gap-4">
<div class="user-profile-info">
<h4 class="mb-2 mt-lg-6">John Doe</h4>
<ul class="list-inline mb-0 d-flex align-items-center flex-wrap justify-content-sm-start justify-content-center gap-4 my-2">
<li class="list-inline-item d-flex gap-2 align-items-center">
<i class="ti ti-palette ti-lg"></i><span class="fw-medium">UX Designer</span>
</li>
<li class="list-inline-item d-flex gap-2 align-items-center">
<i class="ti ti-map-pin ti-lg"></i><span class="fw-medium">Vatican City</span>
</li>
<li class="list-inline-item d-flex gap-2 align-items-center">
<i class="ti ti-calendar ti-lg"></i><span class="fw-medium"> Joined April 2021</span></li>
</ul>
</div>
<a href="javascript:void(0)" class="btn btn-primary mb-1 waves-effect waves-light">
<i class="ti ti-user-check ti-xs me-2"></i>Connected
</a>
</div>
</div>
</div>
</div>
-->

View File

@ -0,0 +1,56 @@
<!-- resources/views/components/user/details/connections.blade.php -->
@props([
'connections' => [
['avatar' => 'assets/img/avatars/2.png', 'name' => 'Cecilia Payne', 'count' => '45', 'connected' => true],
['avatar' => 'assets/img/avatars/3.png', 'name' => 'Curtis Fletcher', 'count' => '1.32k', 'connected' => false],
['avatar' => 'assets/img/avatars/10.png', 'name' => 'Alice Stone', 'count' => '125', 'connected' => false],
['avatar' => 'assets/img/avatars/7.png', 'name' => 'Darrell Barnes', 'count' => '456', 'connected' => true],
['avatar' => 'assets/img/avatars/12.png', 'name' => 'Eugenia Moore', 'count' => '1.2k', 'connected' => true],
]
])
<div class="card card-action mb-6">
<div class="card-header align-items-center">
<h5 class="card-action-title mb-0">Connections</h5>
<div class="card-action-element">
<div class="dropdown">
<button type="button" class="btn btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 text-muted waves-effect waves-light" data-bs-toggle="dropdown" aria-expanded="false">
<i class="ti ti-dots-vertical ti-md text-muted"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item waves-effect" href="#">Share connections</a></li>
<li><a class="dropdown-item waves-effect" href="#">Suggest edits</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item waves-effect" href="#">Report bug</a></li>
</ul>
</div>
</div>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">
@foreach ($connections as $connection)
<li class="mb-4">
<div class="d-flex align-items-center">
<div class="d-flex align-items-center">
<div class="avatar me-2">
<img src="{{ asset($connection['avatar']) }}" alt="Avatar" class="rounded-circle">
</div>
<div class="me-2">
<h6 class="mb-0">{{ $connection['name'] }}</h6>
<small>{{ $connection['count'] }} Connections</small>
</div>
</div>
<div class="ms-auto">
<button class="btn {{ $connection['connected'] ? 'btn-label-primary' : 'btn-primary' }} btn-icon waves-effect waves-light">
<i class="ti {{ $connection['connected'] ? 'ti-user-check' : 'ti-user-x' }} ti-md"></i>
</button>
</div>
</div>
</li>
@endforeach
<li class="text-center">
<a href="javascript:void(0);">View all connections</a>
</li>
</ul>
</div>
</div>

View File

@ -0,0 +1,24 @@
<!-- resources/views/components/user/details/overview.blade.php -->
<div class="card mb-6">
<div class="card-body">
<small class="card-text text-uppercase text-muted small">Overview</small>
<ul class="list-unstyled mb-0 mt-3 pt-1">
<li class="d-flex align-items-end mb-4">
<i class="ti ti-check ti-lg"></i>
<span class="fw-medium mx-2">Task Compiled:</span>
<span>{{ $tasksCompiled ?? '13.5k' }}</span>
</li>
<li class="d-flex align-items-end mb-4">
<i class="ti ti-layout-grid ti-lg"></i>
<span class="fw-medium mx-2">Projects Compiled:</span>
<span>{{ $projectsCompiled ?? '146' }}</span>
</li>
<li class="d-flex align-items-end">
<i class="ti ti-users ti-lg"></i>
<span class="fw-medium mx-2">Connections:</span>
<span>{{ $connections ?? '897' }}</span>
</li>
</ul>
</div>
</div>

View File

@ -0,0 +1,117 @@
@props(['user' => null])
@php
use Illuminate\Support\Str;
use Koneko\VuexyAdmin\Models\PermissionGroup;
$permissions = $user?->getAllPermissionMetas() ?? collect();
$groupedPermissions = $permissions->groupBy(fn($perm) => optional($perm->group)->module ?? 'Sin módulo');
$moduleDescriptions = PermissionGroup::query()
->where('type', 'module')
->get()
->keyBy('module')
->map(fn($group) => [
'name' => $group->name[app()->getLocale()] ?? $group->module,
'description' => $group->ui_metadata['description'][app()->getLocale()] ?? '',
'icon' => $group->ui_metadata['icon'] ?? 'ti ti-box',
]);
@endphp
<div class="container-xxl flex-grow-1 container-p-y">
<div class="row">
@forelse($groupedPermissions as $module => $groupItems)
@php
$subGroups = $groupItems->groupBy(fn($perm) => optional($perm->group)->sub_grupo ?? 'General');
$moduleInfo = $moduleDescriptions[$module] ?? null;
@endphp
<div class="col-md-12 col-xl-6 mb-4">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-start pb-1">
{{-- Sección izquierda: ícono + título --}}
<div class="d-flex flex-column">
<div class="d-flex align-items-center gap-2 mb-1">
<i class="{{ $moduleInfo['icon'] ?? 'ti ti-box' }} text-lg text-muted"></i>
<div>
<div class="fw-semibold">{{ $moduleInfo['name'] ?? $module }}</div>
<small class="text-muted small">{{ $module }}</small>
</div>
</div>
</div>
{{-- Contador de permisos --}}
<div class="text-end">
<span class="text-primary fw-semibold small" aria-label="Total de permisos">
{{ $groupItems->count() }} permiso{{ $groupItems->count() === 1 ? '' : 's' }}
</span>
</div>
</div>
<div class="card-body py-0">
{{-- Descripción general del módulo --}}
@if(!empty($moduleInfo['description']))
<p class="mb-0">{{ $moduleInfo['description'] }}</p>
@endif
</div>
<div class="card-body pt-0">
@foreach($subGroups as $sub => $items)
<hr>
<div class="mt-3">
<h6 class="text-primary mb-1">
{{ Str::headline($sub) }}
<span class="text-muted fw-normal">({{ $items->count() }})</span>
</h6>
@php
$groupInfo = $items->first()?->group;
$subGroupDescription = $groupInfo->ui_metadata['description'][app()->getLocale()] ?? null;
$actions = $items
->filter(fn($perm) => method_exists($perm, 'getActionLabel'))
->map(fn($perm) => $perm->getActionLabel())
->unique()
->implode(', ');
@endphp
@if($subGroupDescription)
<p class="small mb-3">{{ $subGroupDescription }}</p>
@endif
@if($actions)
<p class="text-muted small mb-2">Acciones: {{ $actions }}</p>
@endif
<ul class="list-unstyled small">
@foreach($items as $perm)
<li class="mb-2">
<div class="d-flex justify-content-between align-items-center px-2 py-1">
<div class="d-flex align-items-center gap-1 flex-grow-1 text-wrap">
<i class="ti ti-key text-muted"></i>
<span class="text-break">
{{ $perm->getDisplayName() }}
</span>
</div>
<i class="ti ti-code text-muted cursor-pointer"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-original-title="{{ $perm->name }}">
</i>
</div>
</li>
@endforeach
</ul>
</div>
@endforeach
</div>
</div>
</div>
@empty
<div class="col-12">
<div class="alert alert-warning">Este usuario no tiene permisos asignados.</div>
</div>
@endforelse
</div>
</div>

View File

@ -0,0 +1,26 @@
<!-- resources/views/components/user/details/tabs.blade.php -->
@props([
'tabs' => [
['label' => 'Profile', 'icon' => 'ti ti-user-check', 'active' => true, 'url' => '#'],
['label' => 'Teams', 'icon' => 'ti ti-users', 'active' => false, 'url' => '#'],
['label' => 'Projects', 'icon' => 'ti ti-layout-grid', 'active' => false, 'url' => '#'],
['label' => 'Connections', 'icon' => 'ti ti-link', 'active' => false, 'url' => '#'],
],
])
<div class="row">
<div class="col-md-12">
<div class="nav-align-top">
<ul class="nav nav-pills flex-column flex-sm-row mb-6 gap-2 gap-lg-0">
@foreach ($tabs as $tab)
<li class="nav-item">
<a href="{{ $tab['url']?? 'javascript:;' }}"
class="nav-link {{ $tab['active']?? false ? 'active' : '' }} waves-effect waves-light">
<i class="ti-sm {{ $tab['icon']?? 'ti ti-user-check' }} me-1_5"></i> {{ $tab['label']?? 'No label' }}
</a>
</li>
@endforeach
</ul>
</div>
</div>
</div>

View File

@ -0,0 +1,56 @@
<!-- resources/views/components/user/details/teams.blade.php -->
@props([
'teams' => [
['name' => 'React Developers', 'members' => 72, 'badge' => 'Developer', 'badgeColor' => 'danger', 'avatar' => 'img/icons/brands/react-label.png'],
['name' => 'Support Team', 'members' => 122, 'badge' => 'Support', 'badgeColor' => 'primary', 'avatar' => 'img/icons/brands/support-label.png'],
['name' => 'UI Designers', 'members' => 7, 'badge' => 'Designer', 'badgeColor' => 'info', 'avatar' => 'img/icons/brands/figma-label.png'],
['name' => 'Vue.js Developers', 'members' => 289, 'badge' => 'Developer', 'badgeColor' => 'danger', 'avatar' => 'img/icons/brands/vue-label.png'],
['name' => 'Digital Marketing', 'members' => 24, 'badge' => 'Marketing', 'badgeColor' => 'secondary', 'avatar' => 'img/icons/brands/twitter-label.png'],
],
])
<div class="card card-action mb-6">
<div class="card-header align-items-center">
<h5 class="card-action-title mb-0">Teams</h5>
<div class="card-action-element">
<div class="dropdown">
<button type="button" class="btn btn-icon btn-text-secondary dropdown-toggle hide-arrow p-0 waves-effect waves-light" data-bs-toggle="dropdown" aria-expanded="false">
<i class="ti ti-dots-vertical text-muted"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item waves-effect" href="javascript:void(0);">Share teams</a></li>
<li><a class="dropdown-item waves-effect" href="javascript:void(0);">Suggest edits</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item waves-effect" href="javascript:void(0);">Report bug</a></li>
</ul>
</div>
</div>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">
@foreach ($teams as $team)
<li class="mb-4">
<div class="d-flex align-items-center">
<div class="d-flex align-items-center">
<div class="avatar me-2">
<img src="{{ asset($team['avatar']) }}" alt="Avatar" class="rounded-circle">
</div>
<div class="me-2">
<h6 class="mb-0">{{ $team['name'] }}</h6>
<small>{{ $team['members'] }} Members</small>
</div>
</div>
<div class="ms-auto">
<a href="javascript:;">
<span class="badge bg-label-{{ $team['badgeColor'] }}">{{ $team['badge'] }}</span>
</a>
</div>
</div>
</li>
@endforeach
<li class="text-center">
<a href="javascript:;">View all teams</a>
</li>
</ul>
</div>
</div>

View File

@ -0,0 +1,105 @@
@props([
'items' => [
[
'icon' => 'primary',
'title' => '12 Invoices have been paid',
'time' => '12 min ago',
'description' => 'Invoices have been paid to the company',
'attachment' => [
'icon' => 'pdf',
'label' => 'invoices.pdf'
]
],
[
'icon' => 'success',
'title' => 'Client Meeting',
'time' => '45 min ago',
'description' => 'Project meeting with john @10:15am',
'user' => [
'avatar' => 'http://vuexy-vite.test/assets/img/avatars/1.png',
'name' => 'Lester McCarthy',
'role' => 'CEO of Pixinvent'
]
],
[
'icon' => 'info',
'title' => 'Create a new project for client',
'time' => '2 Days Ago',
'description' => '6 team members in a project',
'users' => [
['avatar' => 'http://vuexy-vite.test/assets/img/avatars/1.png', 'name' => 'Vinnie Mostowy'],
['avatar' => 'http://vuexy-vite.test/assets/img/avatars/4.png', 'name' => 'Allen Rieske'],
['avatar' => 'http://vuexy-vite.test/assets/img/avatars/2.png', 'name' => 'Julee Rossignol'],
['avatar' => null, 'initials' => '+3']
]
]
]
])
<div class="card card-action mb-6">
<div class="card-header align-items-center">
<h5 class="card-action-title mb-0">
<i class="ti ti-chart-bar ti-lg text-body me-4"></i>Activity Timeline
</h5>
</div>
<div class="card-body pt-3">
<ul class="timeline mb-0">
@foreach($items as $item)
<li class="timeline-item timeline-item-transparent">
<span class="timeline-point timeline-point-{{ $item['icon'] }}"></span>
<div class="timeline-event">
<div class="timeline-header mb-3">
<h6 class="mb-0">{{ $item['title'] }}</h6>
<small class="text-muted">{{ $item['time'] }}</small>
</div>
<p class="mb-2">{{ $item['description'] }}</p>
@isset($item['attachment'])
<div class="d-flex align-items-center mb-2">
<div class="badge bg-lighter rounded d-flex align-items-center">
<img src="http://vuexy-vite.test/assets/img/icons/misc/{{ $item['attachment']['icon'] }}.png" alt="icon" width="15" class="me-2">
<span class="h6 mb-0 text-body">{{ $item['attachment']['label'] }}</span>
</div>
</div>
@endisset
@isset($item['user'])
<div class="d-flex justify-content-between flex-wrap gap-2 mb-2">
<div class="d-flex flex-wrap align-items-center mb-50">
<div class="avatar avatar-sm me-3">
<img src="{{ $item['user']['avatar'] }}" alt="Avatar" class="rounded-circle">
</div>
<div>
<p class="mb-0 small fw-medium">{{ $item['user']['name'] }}</p>
<small>{{ $item['user']['role'] }}</small>
</div>
</div>
</div>
@endisset
@isset($item['users'])
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between align-items-center flex-wrap border-top-0 p-0">
<div class="d-flex flex-wrap align-items-center">
<ul class="list-unstyled users-list d-flex align-items-center avatar-group m-0 me-2">
@foreach($item['users'] as $u)
<li class="avatar pull-up" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ $u['name'] ?? '' }}">
@if(isset($u['avatar']))
<img class="rounded-circle" src="{{ $u['avatar'] }}" alt="Avatar">
@else
<span class="avatar-initial rounded-circle pull-up text-heading">{{ $u['initials'] }}</span>
@endif
</li>
@endforeach
</ul>
</div>
</li>
</ul>
@endisset
</div>
</li>
@endforeach
</ul>
</div>
</div>