Prepare modules

This commit is contained in:
2025-03-22 12:44:30 -06:00
parent 099267ee07
commit 7d8566350d
137 changed files with 3723 additions and 4325 deletions

View File

@ -1,70 +0,0 @@
<div>
<div class="form-custom-listener" id="application-settings-card">
<div class="card">
<div class="card-body">
<h5 class="card-title">Información de aplicación</h5>
<div class="fv-row mb-3">
<label for="admin_app_name" class="form-label">
Titulo de aplicación <span class="text-xs">(Nombre corto)</span>
</label>
<input type="text" id="admin_app_name" wire:model="admin_app_name" class="form-control" placeholder="Titulo de aplicación">
@error('admin_app_name')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="fv-row mb-6">
<h5>Logotipo tema claro</h5>
<div class="fv-row mb-4">
<input type="file" wire:model="upload_image_logo" id="upload_image_logo" class="form-control" accept="image/*" />
@error('upload_image_logo')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="mb-8 text-center align-items-center">
<div class="justify-content-center align-items-center bg-slate-100 p-4">
<img src="{{ $upload_image_logo ? $upload_image_logo->temporaryUrl() : asset('storage/' . $admin_image_logo) }}">
</div>
</div>
<h5>Logotipo tema obscuro</h5>
<div class="fv-row mb-4">
<input type="file" wire:model="upload_image_logo_dark" id="upload_image_logo_dark" class="form-control" accept="image/*" />
@error('upload_image_logo_dark')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="mb-3 text-center align-items-center">
<div class="justify-content-center align-items-center bg-slate-800 p-4">
<img src="{{ $upload_image_logo_dark ? $upload_image_logo_dark->temporaryUrl() : asset('storage/' . $admin_image_logo_dark) }}">
</div>
</div>
</div>
</div>
</div>
<div>
{{-- Botones --}}
<div class="row my-4">
<div class="col-lg-12 text-end">
<button
type="button"
wire:click="save"
class="btn btn-primary btn-save btn-sm mt-2 mr-2 waves-effect waves-light"
:disabled="{{ $upload_image_logo === null && $upload_image_logo_dark === null ? 'true' : 'false' }}"
data-loading-text="Guardando...">
<i class="ti ti-device-floppy mr-2"></i>
Guardar cambios
</button>
<button
type="button"
wire:click="loadSettings"
class="btn btn-secondary btn-cancel btn-sm mt-2 mr-2 waves-effect waves-light"
:disabled="{{ $upload_image_logo === null && $upload_image_logo_dark === null ? 'true' : 'false' }}">
<i class="ti ti-rotate-2 mr-2"></i>
Cancelar
</button>
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
</div>
</div>
</div>

View File

@ -1,105 +0,0 @@
<div>
<div class="form-custom-listener" id="general-settings-card">
<div class="card">
<div class="card-body">
<h5 class="card-title">Información de página web</h5>
<div class="fv-row mb-3">
<label for="admin_title" class="form-label">
Titulo del sitio <span class="text-xs">(Nombre completo)</span>
</label>
<input type="text" id="admin_title" wire:model="admin_title" class="form-control"
placeholder="Titulo del sitio">
@error('admin_title')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="fv-row mb-6">
<label for="upload_image_favicon" class="form-label">
Icono de página <span class="text-xs">(Favicon)</span>
</label>
<input type="file" wire:model="upload_image_favicon" id="upload_image_favicon" class="form-control"
accept="image/*" />
@error('upload_image_favicon')
<span class="text-danger">{{ $message }}</span>
@enderror
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-16x16 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_16x16) }}">
</div>
<span class="text-muted mt-1">Navegadores web (16x16)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-76x76 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_76x76) }}">
</div>
<span class="text-muted mt-1">iPad sin Retina (76x76)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-120x120 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_120x120) }}">
</div>
<span class="text-muted mt-1">iPhone (120x120)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-152x152 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_152x152) }}">
</div>
<span class="text-muted mt-1">iPad (152x152)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-180x180 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_180x180) }}">
</div>
<span class="text-muted mt-1">iPhone con Retina HD (180x180)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-192x192 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_192x192) }}">
</div>
<span class="text-muted mt-1">Android y otros dispositivos móviles (192x192)</span>
</div>
</div>
</div>
</div>
</div>
<div>
{{-- Botones --}}
<div class="row my-4">
<div class="col-lg-12 text-end">
<button
type="button"
wire:click="save"
class="btn btn-primary btn-save btn-sm mt-2 mr-2 waves-effect waves-light"
{{ !$upload_image_favicon ? 'disabled' : '' }}
data-loading-text="Guardando...">
<i class="ti ti-device-floppy mr-2"></i>
Guardar cambios
</button>
<button
type="button"
wire:click="loadSettings"
class="btn btn-secondary btn-cancel btn-sm mt-2 mr-2 waves-effect waves-light"
{{ !$upload_image_favicon ? 'disabled' : '' }}>
<i class="ti ti-rotate-2 mr-2"></i>
Cancelar
</button>
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
</div>
</div>
</div>

View File

@ -1,196 +0,0 @@
<div>
<div class="form-custom-listener" id="interface-settings-card">
<div class="card">
<div class="card-body">
<h5 class="card-title">Ajustes menú y barra superior</h5>
{{-- Diseño (Layout) --}}
<div class="mb-4">
<label for="vuexy_myLayout" class="form-label">Diseño</label>
<select id="vuexy_myLayout" class="form-select" wire:model="vuexy_myLayout">
<option value="vertical">Vertical</option>
<option value="horizontal">Horizontal</option>
</select>
</div>
{{-- Campos exclusivos para diseño Horizontal --}}
<div x-show="$wire.vuexy_myLayout === 'horizontal'" x-transition>
<div class="mb-4">
<label for="vuexy_headerType" class="form-label">Tipo de barra superior</label>
<select id="vuexy_headerType" class="form-select" wire:model="vuexy_headerType">
<option value="static">Estático</option>
<option value="fixed">Fijo</option>
</select>
</div>
</div>
{{-- Campos exclusivos para diseño Vertical --}}
<div x-show="$wire.vuexy_myLayout === 'vertical'" x-transition>
<div class="mb-4">
<label for="vuexy_navbarType" class="form-label">Tipo de barra de navegación</label>
<select id="vuexy_navbarType" class="form-select" wire:model="vuexy_navbarType">
<option value="sticky">Fija</option>
<option value="static">Estática</option>
<option value="hidden">Oculta</option>
</select>
</div>
</div>
<div x-show="$wire.vuexy_hasCustomizer" x-transition>
<div x-show="$wire.vuexy_myLayout === 'horizontal'" x-transition>
<div class="mb-3">
<x-vuexy-admin::form.checkbox
wire:model='vuexy_showDropdownOnHover'
parent_class='form-switch'>
Mostrar desplegable al pasar el mouse
</x-vuexy-admin::form.checkbox>
</div>
</div>
</div>
{{-- Campos exclusivos para diseño Vertical --}}
<div x-show="$wire.vuexy_myLayout === 'vertical'" x-transition>
<div class="mb-3">
<x-vuexy-admin::form.checkbox
wire:model='vuexy_menuFixed'
parent_class='form-switch'>
Menú fijo
</x-vuexy-admin::form.checkbox>
</div>
<div class="mb-3">
<x-vuexy-admin::form.checkbox
wire:model='vuexy_menuCollapsed'
parent_class='form-switch'>
Menú colapsado
</x-vuexy-admin::form.checkbox>
</div>
</div>
<hr>
{{-- Habilitar UI Customizer --}}
<div class="mb-3">
<x-vuexy-admin::form.checkbox
wire:model='vuexy_hasCustomizer'
parent_class='form-switch'>
Habilitar personalizador de plantilla
</x-vuexy-admin::form.checkbox>
</div>
<div x-show="$wire.vuexy_hasCustomizer" x-transition>
<div class="mb-3">
<x-vuexy-admin::form.checkbox
wire:model='vuexy_displayCustomizer'
parent_class='form-switch'>
Mostrar personalizador de plantilla
</x-vuexy-admin::form.checkbox>
</div>
</div>
{{-- Máximo de Enlaces Rápidos --}}
<div x-show="$wire.vuexy_myLayout === 'horizontal' || $wire.vuexy_navbarType !== 'hidden'" x-transition>
<hr>
<div class="mb-4">
<label for="vuexy_maxQuickLinks" class="form-label">Máximo de enlaces rápidos</label>
<input type="number" id="vuexy_maxQuickLinks" class="form-control" wire:model="vuexy_maxQuickLinks" min="2" max="20">
<p class="text-muted">Selecciona un valor entre 2 y 20.</p>
@error('vuexy_maxQuickLinks') <span class="text-danger">{{ $message }}</span> @enderror
</div>
</div>
<hr>
<h5 class="card-title mt-6">Ajustes de tema</h5>
{{-- Tema --}}
<div class="mb-4">
<label for="vuexy_myTheme" class="form-label">Tema</label>
<select id="vuexy_myTheme" class="form-select" wire:model="vuexy_myTheme">
<option value="theme-default">Tema predeterminado</option>
<option value="theme-bordered">Tema bordeado</option>
<option value="theme-semi-dark">Tema semi-oscuro</option>
</select>
</div>
{{-- Estilo --}}
<div class="mb-4">
<label for="vuexy_myStyle" class="form-label">Estilo</label>
<select id="vuexy_myStyle" class="form-select" wire:model="vuexy_myStyle">
<option value="light">Claro</option>
<option value="dark">Oscuro</option>
<option value="system">Modo del sistema</option>
</select>
</div>
<hr>
<h5 class="card-title mt-6">Ajustes de diseño</h5>
{{-- Vista de Autenticación --}}
<div class="mb-4">
<label for="vuexy_authViewMode" class="form-label">Modo de vista de autenticación</label>
<select id="vuexy_authViewMode" class="form-select" wire:model="vuexy_authViewMode">
<option value="cover">Pantalla completa</option>
<option value="basic">Básico</option>
</select>
</div>
{{-- Diseño del Contenido --}}
<div class="mb-4">
<label for="vuexy_contentLayout" class="form-label">Diseño del contenido</label>
<select id="vuexy_contentLayout" class="form-select" wire:model="vuexy_contentLayout">
<option value="compact">Compacto</option>
<option value="wide">Ancho</option>
</select>
</div>
{{-- Pie de Página Fijo --}}
<div class="mb-3">
<x-vuexy-admin::form.checkbox
wire:model='vuexy_footerFixed'
parent_class='form-switch'>
Pie de página fijo
</x-vuexy-admin::form.checkbox>
</div>
{{-- Botones --}}
<div class="row mt-6">
<div class="col-lg-12 text-end">
</div>
</div>
</div>
</div>
<div>
{{-- Botones --}}
<div class="row my-4">
<div class="col-lg-12 text-end">
<button
type="button"
wire:click="save"
disabled
class="btn btn-primary btn-save btn-sm mt-2 mr-2 waves-effect waves-light">
<i class="ti ti-device-floppy me-1"></i>
Guardar cambios
</button>
<button
type="button"
wire:click="loadSettings"
disabled
class="btn btn-secondary btn-cancel btn-sm mt-2 mr-2 waves-effect waves-light">
<i class="ti ti-rotate-2 me-1"></i>
Cancelar
</button>
<button
type="button"
wire:click="clearCustomConfig"
class="btn btn-success btn-reset btn-sm mt-2 mr-2 waves-effect waves-light">
<i class="ti ti-adjustments-cog me-1"></i>
Restaurar valores predeterminados
</button>
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
</div>
</div>
</div>

View File

@ -1,67 +0,0 @@
<div>
<form id="mail-sender-response-settings-card">
<div class="card">
<h5 class="card-header">Correo electrónicos de salida</h5>
<div class="card-body">
<div class="mb-3 fv-row">
<label for="from_address" class="form-label">Correo electrónico</label>
<input type="text" name="from_address" id="from_address" wire:model='from_address' class="form-control" placeholder="Correo electrónico">
@error('from_address') <span class="text-danger">{{ $message }}</span> @enderror
</div>
<div class="mb-3 fv-row">
<label for="from_name" class="form-label">Nombre</label>
<input type="text" name="from_name" id="from_name" wire:model='from_name' class="form-control" placeholder="Nombre">
@error('from_name') <span class="text-danger">{{ $message }}</span> @enderror
</div>
<h5 class="mt-6">Correo electrónico de respuesta</h5>
<div class="mb-3 fv-row">
<label for="reply_to_method" class="form-label">Correo electrónico de respuesta</label>
<x-vuexy-admin::form.select
wire:model='reply_to_method'
:options="$reply_email_options"
placeholder="Selecciona el correo electrónico de respuesta" />
@error('reply_to_method') <span class="text-danger">{{ $message }}</span> @enderror
</div>
<div class="email-custom-div">
<div class="mb-3 fv-row">
<label for="reply_to_email" class="form-label">Correo electrónico</label>
<input type="text" name="reply_to_email" id="reply_to_email" wire:model='reply_to_email' class="form-control" placeholder="Correo electrónico">
@error('reply_to_email') <span class="text-danger">{{ $message }}</span> @enderror
</div>
<div class="fv-row">
<label for="reply_to_name" class="form-label">Nombre</label>
<input type="text" name="reply_to_name" id="reply_to_name" wire:model='reply_to_name' class="form-control" placeholder="Nombre">
@error('reply_to_name') <span class="text-danger">{{ $message }}</span> @enderror
</div>
</div>
</div>
</div>
<div>
{{-- Botones --}}
<div class="row my-4">
<div class="col-lg-12 text-end">
<button
type="submit"
id="save_sender_response_button"
class="btn btn-primary btn-sm mt-2 mr-2 waves-effect waves-light"
disabled
data-loading-text="Guardando...">
<i class="ti ti-device-floppy mr-2"></i>
Guardar cambios
</button>
<button
type="button"
id="cancel_sender_response_button"
class="btn btn-secondary btn-sm mt-2 mr-2 waves-effect waves-light"
wire:click="loadSettings"
disabled>
<i class="ti ti-rotate-2 mr-2"></i>
Cancelar
</button>
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
</div>
</form>
</div>

View File

@ -198,7 +198,7 @@
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
<div>
<div class="form-custom-listener" id="cache-stats-card">
<div id="cache-stats-card" class="form-custom-listener mb-4">
{{-- Form Card --}}
<div class="card">
<div class="card-body">
@ -99,7 +99,7 @@
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
<div>
<div class="form-custom-listener" id="memcached-stats-card">
<div id="memcached-stats-card" class="form-custom-listener mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Estadísticas de Memcached</h5>
@ -115,7 +115,7 @@
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
<div>
<div class="form-custom-listener" id="redis-stats-card">
<div id="redis-stats-card" class="form-custom-listener mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Estadísticas de Redis</h5>
@ -138,7 +138,7 @@
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
<div>
<div class="form-custom-listener" id="session-stats-card">
<div id="session-stats-card" class="form-custom-listener mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Configuraciones de Sesiones</h5>
@ -114,7 +114,7 @@
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
@endif
</div>

View File

@ -0,0 +1,7 @@
<x-vuexy-admin::table.bootstrap.manager :tagName="$tagName" :datatableConfig="$bt_datatable">
<x-slot name="tools">
<div class="mb-4 pr-2">
<x-vuexy-admin::button.index-offcanvas :label="$singularName" :tagName="$tagName" />
</div>
</x-slot>
</x-vuexy-admin::table.bootstrap.manager>

View File

@ -0,0 +1,64 @@
<div>
<x-vuexy-admin::offcanvas.basic :id="$offcanvasId" :tag-name="$tagName">
<x-vuexy-admin::form :id="$formId" :mode="$mode" wireSubmit="onSubmit">
<x-slot name="actions">
<x-vuexy-admin::button.offcanvas-buttons :mode="$mode" :tagName="$tagName" />
</x-slot>
{{-- Sección: Identificación --}}
<x-vuexy-admin::form.input :uid="$uniqueId" model="key" label="Clave del parámetro" required icon="ti ti-key" placeholder="Ej: ui.theme" />
<div class="row">
<x-vuexy-admin::form.select :uid="$uniqueId" model="category" label="Categoría"
:options="[
'general' => 'General',
'ui' => 'Interfaz',
'mail' => 'Correo',
'cfdi' => 'CFDI',
]"
placeholder="Selecciona una categoría"
parentClass="col-md-6"
/>
<x-vuexy-admin::form.input :uid="$uniqueId" model="user_id" label="ID Usuario (Opcional)"
type="number"
parentClass="col-md-6"
icon="ti ti-user"
placeholder="ID del usuario asociado"
/>
</div>
<hr>
{{-- Sección: Valores (solo llena uno) --}}
<div class="alert" type="info" icon="ti ti-info-circle" class="mb-1">
Puedes llenar **solo uno** de los valores siguientes según el tipo de configuración.
</div>
<x-vuexy-admin::form.input :uid="$uniqueId" model="value_string" label="Valor (Texto Corto)" icon="ti ti-typography" placeholder="Ej: dark" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="value_integer" label="Valor (Entero)" type="number" icon="ti ti-hash" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="value_float" label="Valor (Decimal)" type="number" step="0.01" icon="ti ti-percentage" />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="value_boolean" label="Valor (Booleano)" />
<x-vuexy-admin::form.textarea :uid="$uniqueId" model="value_text" label="Valor (Texto Largo)" placeholder="Valor largo o configuración avanzada en texto" rows="3" />
<hr>
</x-vuexy-admin::form>
</x-vuexy-admin::offcanvas.basic>
</div>
@push('page-script')
<script>
// Evento para inicializar el formulario cuando se carga la página
document.addEventListener("DOMContentLoaded", function () {
const initializeGlobalSettingsForm = () => {
};
var myOffcanvas = document.getElementById('{{ $offcanvasId }}');
myOffcanvas.addEventListener('show.bs.offcanvas', function () {
initializeGlobalSettingsForm();
});
});
</script>
@endpush

View File

@ -1,152 +1,7 @@
<!-- Permission Table -->
<div class="card">
<div class="card-datatable table-responsive">
<table class="datatables-permissions table border-top">
<thead>
<tr>
<th></th>
<th></th>
<th>Nombre</th>
<th>Asignado a</th>
<th>Creado</th>
<th>Actions</th>
</tr>
</thead>
</table>
</div>
</div>
<!--/ Permission Table -->
<?php
/*
<script>
document.addEventListener('DOMContentLoaded', function() {
$(document).ready(function() {
// Datatable
var dt_permission = $('.datatables-permissions')
.DataTable({
ajax: '{{ url()->current() }}',
columns: [
// columns according to JSON
{data: ''},
{data: 'id'},
{data: 'name'},
{data: 'assigned_to'},
{data: 'created_at'},
//{data: ''}
],
columnDefs: [
{
// For Responsive
className: 'control',
orderable: false,
searchable: false,
responsivePriority: 2,
targets: 0,
render: function(data, type, full, meta) {
return '';
}
},
{
targets: 1,
searchable: false,
visible: false
},
{
// Name
targets: 2,
render: function(data, type, full, meta) {
return "<span data-id=" + full.id + ">" + data + "</span><br>" +
'<small>' + (typeof(full['sub_group']) == 'string'? full['sub_group']: '') + "</small>";
}
},
{
// assigned_to
targets: 3,
orderable: false,
render: function(data, type, full, meta) {
var $assignedTo = full['assigned_to'],
$output = '',
roleBadgeObj = <?= json_encode($rows_roles) ?>;
for (var i = 0; i < $assignedTo.length; i++) {
var val = $assignedTo[i];
$output += roleBadgeObj[val];
}
return $output;
}
},
{
// Created at
targets: 4,
orderable: false
},
],
order: [
[1, 'asc']
],
dom:
'<"row mx-1"' +
'<"col-sm-12 col-md-3" l>' +
'<"col-sm-12 col-lg-9"<"dt-action-buttons d-flex align-items-center justify-content-lg-end justify-content-center flex-md-nowrap flex-wrap"<"me-1"f><"user_role mt-50 width-200 me-1">B>>' +
'>t' +
'<"row mx-2"' +
'<"col-sm-12 col-md-6"i>' +
'<"col-sm-12 col-md-6"p>' +
'>',
language: $.fn.dataTable.ext.datatable_spanish_default,
// Buttons with Dropdown
buttons: [],
// For responsive popup
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.modal({
header: function(row) {
var data = row.data();
return 'Detalles del permiso';
}
}),
type: 'column',
renderer: function(api, rowIdx, columns) {
var data = $.map(columns, function(col, i) {
return col.title !== ''? // ? Do not show row in modal popup if title is blank (for check box)
'<tr data-dt-row="' + col.rowIndex + '" data-dt-column="' + col.columnIndex + '">' +
'<td>' + col.name + ':' + '</td> ' +
'<td>' + col.data + '</td>' +
'</tr>' :
'';
}).join('');
return data ? $('<table class="table table-striped"/><tbody />').append(data) : false;
}
}
},
initComplete: function() {
// Adding role filter once table initialized
this.api()
.columns(3)
.every(function() {
var column = this;
var select = $('{!! $roles_html_select !!}')
.appendTo('.user_role')
.on('change', function() {
var val = $.fn.dataTable.util.escapeRegex($(this).val());
column.search(val? val: '', true, false).draw();
});
});
}
});
});
});
</script>
*/
?>
<x-vuexy-admin::table.bootstrap.manager :tagName="$tagName" :datatableConfig="$bt_datatable">
<x-slot name="tools">
<div class="mb-4 pr-2">
<x-vuexy-admin::button.index-offcanvas :label="$singularName" :tagName="$tagName" />
</div>
</x-slot>
</x-vuexy-admin::table.bootstrap.manager>

View File

@ -0,0 +1,39 @@
<div>
<x-vuexy-admin::offcanvas.basic :id="$offcanvasId" :tag-name="$tagName">
<x-vuexy-admin::form :uid="$uniqueId" :id="$formId" :mode="$mode" wireSubmit="onSubmit">
<x-slot name="actions">
<x-vuexy-admin::button.offcanvas-buttons :mode="$mode" :tagName="$tagName" />
</x-slot>
{{-- Usuario --}}
<x-vuexy-admin::form.input :uid="$uniqueId" model="name" label="Nombre(s)" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="last_name" label="Apellidos" />
{{-- Correos electrónicos --}}
<x-vuexy-admin::form.input type="email" :uid="$uniqueId" model="email" label="Correo electrónico" icon="ti ti-mail" autocomplete="email" inputmode="email" />
{{-- Contraseña --}}
<x-vuexy-admin::form.input type="password" :uid="$uniqueId" model="password" label="Contraseña" icon="ti ti-lock" autocomplete="new-password" />
<hr>
</x-vuexy-admin::form>
</x-vuexy-admin::offcanvas.basic>
</div>
@push('page-script')
<script>
// Evento para inicializar el formulario cuando se carga la página
document.addEventListener("DOMContentLoaded", function () {
const initializeUserForm = () => {
};
var myOffcanvas = document.getElementById('{{ $offcanvasId }}');
myOffcanvas.addEventListener('show.bs.offcanvas', function () {
initializeUserForm();
});
});
</script>
@endpush

View File

@ -0,0 +1,95 @@
<x-form-section submit="updateProfileInformation">
<x-slot name="title">
{{ __('Profile Information') }}
</x-slot>
<x-slot name="description">
{{ __('Update your account\'s profile information and email address.') }}
</x-slot>
<x-slot name="form">
<!-- Profile Photo -->
@if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
<div x-data="{photoName: null, photoPreview: null}" class="col-span-6 sm:col-span-4">
<!-- Profile Photo File Input -->
<input type="file" id="photo" class="hidden"
wire:model.live="photo"
x-ref="photo"
x-on:change="
photoName = $refs.photo.files[0].name;
const reader = new FileReader();
reader.onload = (e) => {
photoPreview = e.target.result;
};
reader.readAsDataURL($refs.photo.files[0]);
" />
<x-label for="photo" value="{{ __('Photo') }}" />
<!-- Current Profile Photo -->
<div class="mt-2" x-show="! photoPreview">
<img src="{{ $this->user->profile_photo_url }}" alt="{{ $this->user->name }}" class="rounded-full size-20 object-cover">
</div>
<!-- New Profile Photo Preview -->
<div class="mt-2" x-show="photoPreview" style="display: none;">
<span class="block rounded-full size-20 bg-cover bg-no-repeat bg-center"
x-bind:style="'background-image: url(\'' + photoPreview + '\');'">
</span>
</div>
<x-secondary-button class="mt-2 me-2" type="button" x-on:click.prevent="$refs.photo.click()">
{{ __('Select A New Photo') }}
</x-secondary-button>
@if ($this->user->profile_photo_path)
<x-secondary-button type="button" class="mt-2" wire:click="deleteProfilePhoto">
{{ __('Remove Photo') }}
</x-secondary-button>
@endif
<x-input-error for="photo" class="mt-2" />
</div>
@endif
<!-- Name -->
<div class="col-span-6 sm:col-span-4">
<x-label for="name" value="{{ __('Name') }}" />
<x-input id="name" type="text" class="mt-1 block w-full" wire:model="state.name" required autocomplete="name" />
<x-input-error for="name" class="mt-2" />
</div>
<!-- Email -->
<div class="col-span-6 sm:col-span-4">
<x-label for="email" value="{{ __('Email') }}" />
<x-input id="email" type="email" class="mt-1 block w-full" wire:model="state.email" required autocomplete="username" />
<x-input-error for="email" class="mt-2" />
@if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::emailVerification()) && ! $this->user->hasVerifiedEmail())
<p class="text-sm mt-2 dark:text-white">
{{ __('Your email address is unverified.') }}
<button type="button" class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800" wire:click.prevent="sendEmailVerification">
{{ __('Click here to re-send the verification email.') }}
</button>
</p>
@if ($this->verificationLinkSent)
<p class="mt-2 font-medium text-sm text-green-600 dark:text-green-400">
{{ __('A new verification link has been sent to your email address.') }}
</p>
@endif
@endif
</div>
</x-slot>
<x-slot name="actions">
<x-action-message class="me-3" on="saved">
{{ __('Saved.') }}
</x-action-message>
<x-button wire:loading.attr="disabled" wire:target="photo">
{{ __('Save') }}
</x-button>
</x-slot>
</x-form-section>

View File

@ -1,704 +0,0 @@
<div>
<div class="users-index alert-errors">{!! $indexAlert !!}</div>
<!-- Users List Table -->
<div class="card" wire:ignore>
<div class="card-datatable table-responsive">
<table class="datatables-users table">
<thead class="border-top">
<tr>
<th></th>
<th>Id</th>
<th>Usuario</th>
<th>Roles</th>
<th>Estatus</th>
<th>Creado</th>
<th>Acciones</th>
</tr>
</thead>
</table>
</div>
</div>
<!-- Offcanvas to add new user -->
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasUser" aria-labelledby="offcanvasLabel">
<div class="offcanvas-header border-bottom">
<h5 id="offcanvasLabel" class="offcanvas-title">{{ $modalTitle }}</h5>
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body mx-0 flex-grow-0 p-6 h-100">
<form class="pt-0" id="userForm" autocomplete='off'>
<input type="hidden" name="id" wire:model='userId' />
<div class="mb-3">
<label for="name" class="form-label">Nombre completo</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="ti ti-user"></i></span>
<input type="text" name="name" wire:model='name' id="name" class="form-control" placeholder="Pepe Pecas" />
</div>
<div class="error-message"></div>
</div>
<div class="mb-3">
<label for="email" class="form-label">Correo electrónico</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="ti ti-mail"></i></span>
<input type="text" name="email" wire:model='email' id="email" class="form-control" placeholder="picapapas@mail.com" />
</div>
<div class="error-message"></div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Contraseña</label>
<div class="input-group input-group-merge form-password-toggle">
<span class="input-group-text"><i class="ti ti-key"></i></span>
<input type="password" name="password" wire:model='password' class="form-control form-control-merge" id="password" placeholder="············" />
<span class="input-group-text cursor-pointer"><i class="ti ti-eye"></i></span>
</div>
<div class="error-message"></div>
</div>
<div class="mb-3">
<label for="roles" class="form-label">Roles del usuario</label>
<x-vuexy-admin::form.select
id="roles"
name="roles[]"
wire:model='roles'
:options="$roles_options"
multiple
class="select2 form-select" />
</div>
<div class="mb-3">
<label for="status" class="form-label">Estatus</label>
<div class="input-group input-group-merge">
<span class="input-group-text"><i class="ti ti-alert-triangle"></i></span>
<x-vuexy-admin::form.select
id="status"
name="status"
wire:model='status'
:options="$status_options"
class="form-select" />
</div>
</div>
<div class="mb-3">
<label for="photo" class="form-label">Imagen de perfil</label>
<div class="image-wrapper mb-1">
<img id="user-image" class="max-w-full" src="{{ $src_photo }}" alt="">
</div>
<input type="file" name="photo" id="photo" class='form-control' accept='image/*' />
</div>
<div class="alert-errors"></div>
<button type="submit" class="btn btn-primary me-3 data-submit">{{ $btnSubmitTxt }}</button>
<button type="reset" class="btn btn-label-danger" data-bs-dismiss="offcanvas">Cancelar</button>
</form>
</div>
</div>
<!-- Delete User Modal -->
<div class="modal fade" id="deleteUserModal" tabindex="-1" aria-hidden="true" wire:ignore>
<div class="modal-dialog modal-dialog-centered modal-simple">
<div class="modal-content p-3 p-md-5">
<div class="modal-body">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<div class="text-center mb-4">
<h3 class="mb-2">Eliminar usuario</h3>
<p class="text-muted">El proceso de eliminación es definitivo e irreversible</p>
</div>
<form class="row g-3">
<div class="col-12">
<p class="name text-center font-bold"></p>
</div>
<div class="col-12 text-center">
<button type="submit" class="btn btn-primary me-sm-3 me-1 btn-submit">Eliminar usuario</button>
<button type="reset" class="btn btn-secondary btn-reset" data-bs-dismiss="modal" aria-label="Close">Cancelar</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!--/ Delete User Modal -->
</div>
@push('page-script')
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
const store_route = '{{ route('admin.core.users.store') }}',
update_route = '{{ route('admin.core.users.update-ajax', '0~0') }}',
show_route = '{{ route('admin.core.users.show', '0~0') }}',
destroy_route = '{{ route('admin.core.users.destroy', '0~0') }}';
var statusObj = <?= json_encode($statuses) ?>,
$usersIndexAlert = $('.users-index.alert-errors'),
$dt_user_table = $('.datatables-users'),
dt_user;
var offcanvasElement = document.getElementById('offcanvasUser'),
offcanvasUser = new bootstrap.Offcanvas(offcanvasElement);
load_js_form = () => {
$('#userForm .select2')
.each(function() {
var $this = $(this)
$this.wrap('<div class="position-relative"></div>')
$this.select2({
dropdownAutoWidth: true,
width: '100%',
dropdownParent: $this.parent()
});
});
// Previo de imagenes
document.getElementById("photo").addEventListener('change', updatePreviewImage);
// Reset form
$("#userForm")
.on('reset', function(){
setTimeout(function(){
$('#roles').trigger('change');
}, 250)
$('#user-image').prop("src", "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
$('#userForm .alert-errors').html('');
});
$("#userForm")
.validate({
errorClass: 'error',
highlight: function(element, errorClass, validClass) {
// Agrega la clase de error a la fila (contenedor del campo)
$(element).closest('.mb-3').addClass('has-error');
},
unhighlight: function(element, errorClass, validClass) {
// Elimina la clase de error de la fila (contenedor del campo)
$(element).closest('.mb-3').removeClass('has-error');
},
errorPlacement: function(error, element) {
// Controla dónde se colocan los mensajes de error
error.appendTo(element.closest('.mb-3').find('.error-message'));
},
rules: {
name: {
required: true,
minlength: 8
},
email: {
required: true,
email: true
},
password: {
required: function(element) {
return !$("#userForm input[name=id]").val();
},
minlength: 6
}
},
messages: {
name: {
required: "Por favor ingrese su nombre completo",
minlength: "El nombre completo debe tener al menos 8 caracteres"
},
email: {
required: "Por favor ingrese su correo electrónico",
email: "El valor no es una dirección de correo válida"
},
password: {
required: "La contraseña es obligatoria para nuevos usuarios",
minlength: "La contraseña debe tener al menos 6 caracteres"
}
},
submitHandler: function(form) {
var form = $("#userForm")[0],
data = new FormData(form);
$('#userForm :input').prop('disabled', true);
var url = $(form.id).val() ?
update_route.replace('0~0', $(form.id).val()) :
store_route;
$.ajax({
url: url,
method: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: data,
contentType: false,
processData: false,
cache: false,
timeout: 3000,
success: function(data) {
$('#userForm :input').prop('disabled', false);
if (data.errors) {
$('#userForm .alert-errors').html('<div class="alert alert-danger alert-dismissible fade show" role="alert">' +
'<div class="alert-body">' + data.errors + '</div>' +
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
'</div>');
} else {
$usersIndexAlert.html('<div class="alert alert-success alert-dismissible fade show" role="alert">' +
'<div class="alert-body">' +
'<p class="mb-0"><strong>' + data.success + '</strong></p>' +
'</div>' +
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
'</div>');
$('#userForm button[type=reset]').trigger('click');
//@this.call('refreshUserCount');
dt_user.ajax.reload();
}
},
error: function(e) {
$('#userForm :input').prop('disabled', false);
$('#userForm .alert-errors').html('<div class="alert alert-danger alert-dismissible fade show" role="alert">' +
'<div class="alert-body">' + e.responseJSON.message + '</div>' +
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
'</div>');
}
});
}
});
}
// Previo de imagen de perfil
updatePreviewImage = (event) => {
var file = event.target.files[0],
reader = new FileReader();
reader.onload = event => {
document.getElementById('user-image').setAttribute('src', event.target.result);
};
reader.readAsDataURL(file);
}
// Add User
add_user = () => {
let $offcanvasUser = $('#offcanvasUser');
$('.offcanvas-title', $offcanvasUser).html('Crear usuario nuevo');
$('.btn-submit', $offcanvasUser).html('Crear usuario');
if ($('input[name=id]', $offcanvasUser).val()){
document.getElementById('userForm').reset();
$('input[name=id]', $offcanvasUser).val('');
$('#roles').trigger('change');
$('#user-image').prop("src", "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
$('.alert-errors', $offcanvasUser).html('');
}
}
Livewire.on('openModal', () => {
setTimeout(() =>{
offcanvasUser.show();
load_js_form();
dt_user.ajax.reload();
}, 1)
});
Livewire.on('afterDelete', () => {
setTimeout(() =>{
var modalElement = document.getElementById('deleteUserModal'),
modalDelete = bootstrap.Modal.getInstance(modalElement);
modalDelete.hide();
load_js_form();
dt_user.ajax.reload();
}, 1)
});
// (jquery)
$(function () {
let borderColor, bodyBg, headingColor;
if (isDarkStyle) {
borderColor = config.colors_dark.borderColor;
bodyBg = config.colors_dark.bodyBg;
headingColor = config.colors_dark.headingColor;
} else {
borderColor = config.colors.borderColor;
bodyBg = config.colors.bodyBg;
headingColor = config.colors.headingColor;
}
// Users datatable
dt_user = $dt_user_table.DataTable({
ajax: '{{ url()->current() }}',
columns: [
// columns according to JSON
{ data: 'id' },
{ data: 'id' },
{ data: 'name' },
{ data: 'roles' },
{ data: 'status' },
{ data: 'created_at' },
{ data: 'action' }
],
columnDefs: [
{
// For Responsive
className: 'control',
searchable: false,
orderable: false,
responsivePriority: 2,
targets: 0,
render: function (data, type, full, meta) {
return '';
}
},
{
// User name and email
targets: 2,
responsivePriority: 3,
render: function (data, type, full, meta) {
var $name = full['name'],
$email = full['email'],
$image = full['avatar'];
if ($image) {
// For Avatar image
var $output =
'<img src="' + $image + '" alt="Avatar" class="rounded-circle">';
} else {
// For Avatar badge
var $name = full['full_name'],
$initials = $name.match(/\b\w/g) || [];
$initials = (($initials.shift() || '') + ($initials.pop() || '')).toUpperCase();
$output = '<span class="avatar-initial rounded-circle>' + $initials + '</span>';
}
// Creates full output for row
var $row_output =
'<div class="d-flex justify-content-start align-items-center user-name">' +
'<div class="avatar-wrapper">' +
'<div class="avatar avatar-sm me-4">' + $output + '</div>' +
'</div>' +
'<div class="d-flex flex-column">' +
'<a href="' + show_route.replace('0~0', full['id']) + '" class="text-heading text-truncate"><span class="fw-medium">' + $name + '</span></a>' +
'<small>' + $email + '</small>' +
'</div>' +
'</div>';
return $row_output;
}
},
{
// User Role
targets: 3,
render: function(data, type, full, meta) {
var $assignedTo = full['roles'],
$output = '',
roleBadgeObj = <?= json_encode($rows_roles) ?>;
for (var i = 0; i < $assignedTo.length; i++) {
var val = $assignedTo[i];
$output += roleBadgeObj[val];
}
return $output;
}
},
{
// User Status
targets: 4,
render: function (data, type, full, meta) {
var $status = full['status'];
return ('<span class="badge rounded-pill ' + statusObj[$status].class + '" text-capitalized>' + statusObj[$status].title + '</span>');
}
},
{
// Created
targets: 5,
render: function (data, type, full, meta) {
return full['created_at'];
}
},
{
// Actions
targets: -1,
title: 'Acciones',
searchable: false,
orderable: false,
render: function (data, type, full, meta) {
return ('<div class="d-flex align-items-center">' +
'<a href="' + show_route.replace('0~0', full['id']) + '" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill"><i class="ti ti-eye ti-md"></i></a>' +
'<a href="javascript:;"" wire:click.prevent="edit(' + full['id'] + ')" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill"><i class="ti ti-edit ti-md"></i></a>' +
@can('system.users.destroy')
'<a href="javascript:;" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill dropdown-toggle hide-arrow" data-bs-toggle="dropdown"><i class="ti ti-dots-vertical ti-md"></i></a>' +
'<div class="dropdown-menu dropdown-menu-end m-0">' +
'<a href="javascript:;" class="dropdown-item delete-record">Eliminar</a>' +
'</div>' +
@endcan
'</div>');
}
}
],
order: [[2, 'desc']],
dom:
'<"row"' +
'<"col-md-2"<"ms-n2"l>>' +
'<"col-md-10"<"dt-action-buttons text-xl-end text-lg-start text-md-end text-start d-flex align-items-center justify-content-end flex-md-row flex-column mb-6 mb-md-0 mt-n6 mt-md-0"<"user_role dataTables_filter">fB>>' +
'>t' +
'<"row"' +
'<"col-sm-12 col-md-6"i>' +
'<"col-sm-12 col-md-6"p>' +
'>',
language: $.fn.dataTable.ext.datatable_spanish_default,
// Buttons with Dropdown
buttons: [
{
extend: 'collection',
className: 'btn btn-label-secondary dropdown-toggle mx-4 waves-effect waves-light',
text: '<i class="ti ti-upload me-2 ti-xs"></i>Exportar',
buttons: [
{
extend: 'print',
text: '<i class="ti ti-printer me-2" ></i>Imprimir',
className: 'dropdown-item',
exportOptions: {
columns: [1, 2, 3, 4, 5],
// prevent avatar to be print
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
},
customize: function (win) {
//customize print view for dark
$(win.document.body)
.css('color', headingColor)
.css('border-color', borderColor)
.css('background-color', bodyBg);
$(win.document.body)
.find('table')
.addClass('compact')
.css('color', 'inherit')
.css('border-color', 'inherit')
.css('background-color', 'inherit');
}
},
{
extend: 'csv',
text: '<i class="ti ti-file-text me-2" ></i>Csv',
className: 'dropdown-item',
exportOptions: {
columns: [1, 2, 3, 4, 5],
// prevent avatar to be display
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
}
},
{
extend: 'excel',
text: '<i class="ti ti-file-spreadsheet me-2"></i>Excel',
className: 'dropdown-item',
exportOptions: {
columns: [1, 2, 3, 4, 5],
// prevent avatar to be display
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
}
},
/*
{
extend: 'pdf',
text: '<i class="ti ti-file-code-2 me-2"></i>Pdf',
className: 'dropdown-item',
exportOptions: {
columns: [1, 2, 3, 4, 5],
// prevent avatar to be display
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
}
},
*/
{
extend: 'copy',
text: '<i class="ti ti-copy me-2" ></i>Copiar',
className: 'dropdown-item',
exportOptions: {
columns: [1, 2, 3, 4, 5],
// prevent avatar to be display
format: {
body: function (inner, coldex, rowdex) {
if (inner.length <= 0) return inner;
var el = $.parseHTML(inner);
var result = '';
$.each(el, function (index, item) {
if (item.classList !== undefined && item.classList.contains('user-name')) {
result = result + item.lastChild.firstChild.textContent;
} else if (item.innerText === undefined) {
result = result + item.textContent;
} else result = result + item.innerText;
});
return result;
}
}
}
}
]
},
@can('system.users.create') {
text: '<i class="ti ti-plus me-0 me-sm-1 ti-xs"></i><span class="d-none d-sm-inline-block">Nuevo usuario</span>',
className: 'add-new btn btn-primary waves-effect waves-light',
attr: {
'onclick': 'add_user()',
'data-bs-toggle': 'offcanvas',
'data-bs-target': '#offcanvasUser'
}
}
@endcan
],
// For responsive popup
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.modal({
header: function (row) {
var data = row.data();
return 'Details of ' + data['full_name'];
}
}),
type: 'column',
renderer: function (api, rowIdx, columns) {
var data = $.map(columns, function (col, i) {
return col.title !== '' // ? Do not show row in modal popup if title is blank (for check box)
? '<tr data-dt-row="' + col.rowIndex + '" data-dt-column="' + col.columnIndex + '">' +
'<td>' + col.title + ':' + '</td> ' +
'<td>' + col.data + '</td>' +
'</tr>'
: '';
}).join('');
return data ? $('<table class="table"/><tbody />').append(data) : false;
}
}
},
initComplete: function () {
this.api()
.columns(3)
.every(function () {
var column = this,
select = $('{!! $roles_html_select !!}')
.appendTo('.user_role')
.on('change', function() {
var val = $.fn.dataTable.util.escapeRegex($(this).val());
column.search(val? val: '', true, false).draw();
});
});
}
});
// Delete Record
$('.datatables-users tbody')
.on('click', '.delete-record', function () {
var tr = $(this).closest('tr'),
data = dt_user.row(tr).data();
$('#deleteUserModal').modal('show');
$('#deleteUserModal .name').html(data.id + ': ' + data.name);
$('#deleteUserModal').data('userId', data.id);
});
// Attach the event listener to the submit button
$('#deleteUserModal .btn-submit')
.on('click', function (e) {
e.preventDefault();
@this.call('delete', $('#deleteUserModal').data('userId'));
});
// Filter form control to default size
// ? setTimeout used for multilingual table initialization
setTimeout(() => {
$('.dataTables_filter .form-control').removeClass('form-control-sm');
$('.dataTables_length .form-select').removeClass('form-select-sm');
}, 300);
load_js_form();
});
});
</script>
@endpush

View File

@ -1,7 +1,7 @@
<x-vuexy-admin::table.bootstrap.manager :tagName="$tagName" :datatableConfig="$bt_datatable" :routes="$routes" >
<x-slot name="tools">
<div class="mb-4 pr-2">
<x-vuexy-admin::button.index-off-canvas :label="$singularName" :tagName="$tagName" />
<x-vuexy-admin::button.index-offcanvas :label="$singularName" :tagName="$tagName" />
</div>
</x-slot>
</x-vuexy-admin::table.bootstrap.manager>

View File

@ -5,28 +5,15 @@
<x-vuexy-admin::button.offcanvas-buttons :mode="$mode" :tagName="$tagName" />
</x-slot>
{{-- Usuario --}}
<div class="row">
<x-vuexy-admin::form.input :uid="$uniqueId" model="code" label="Código de usuario" icon="ti ti-tag" parent-class="col-md-8" autocomplete="off" />
</div>
<x-vuexy-admin::form.input :uid="$uniqueId" model="name" label="Nombre(s)" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="last_name" label="Apellidos" />
<hr>
{{-- Teléfonos y Correos --}}
<x-vuexy-admin::form.input type="tel" :uid="$uniqueId" model="tel" label="Teléfono" icon="ti ti-phone" phoneMode="both" />
{{-- Correos electrónicos --}}
<x-vuexy-admin::form.input type="email" :uid="$uniqueId" model="email" label="Correo electrónico" icon="ti ti-mail" autocomplete="email" inputmode="email" />
<hr>
{{-- Contraseña --}}
<x-vuexy-admin::form.input type="password" :uid="$uniqueId" model="password" label="Contraseña" icon="ti ti-lock" autocomplete="new-password" />
<x-vuexy-admin::form.textarea :uid="$uniqueId" model="notes" label="Notas / Observaciones" />
<hr>
{{-- Estado del Centro de Trabajo --}}
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_partner" label="Es socio" switch />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_employee" label="Es empleado" switch />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_prospect" label="Es prospecto" switch />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_customer" label="Es cliente" switch />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_provider" label="Es proveedor" switch />
<hr>
</x-vuexy-admin::form>

View File

@ -42,7 +42,7 @@
<i class="ti ti-user pr-2"></i> Cuenta de usuario
</button>
</li>
@if (($is_customer || $is_user) && $this->tipo_persona != App\Models\User::TIPO_RFC_PUBLICO)
@if (($is_customer) && $this->tipo_persona != App\Models\User::TIPO_RFC_PUBLICO)
<li class="nav-item" role="presentation">
<button type="button" @click="setActiveTabPan('accesos')" :class="{ 'active': activeTabPan === 'accesos' }" class="nav-link waves-effect" role="tab" data-bs-toggle="tab" data-bs-target="#navs-left-accesos" aria-controls="navs-left-accesos">
<i class="ti ti-key pr-2"></i> Accesos
@ -56,14 +56,14 @@
</button>
</li>
@endif
@if (($is_prospect || $is_customer || $is_provider || $is_user) && $this->tipo_persona != App\Models\User::TIPO_RFC_PUBLICO)
@if (($is_prospect || $is_customer || $is_provider) && $this->tipo_persona != App\Models\User::TIPO_RFC_PUBLICO)
<li class="nav-item" role="presentation">
<button type="button" @click="setActiveTabPan('direcciones')" :class="{ 'active': activeTabPan === 'direcciones' }" class="nav-link waves-effect" role="tab" data-bs-toggle="tab" data-bs-target="#navs-left-direcciones" aria-controls="navs-left-direcciones">
<i class="ti ti-map-pin pr-2"></i> Direcciones
</button>
</li>
@endif
@if (($is_prospect || $is_customer || $is_provider || $is_user) && $this->tipo_persona != App\Models\User::TIPO_RFC_PUBLICO)
@if (($is_prospect || $is_customer || $is_provider) && $this->tipo_persona != App\Models\User::TIPO_RFC_PUBLICO)
<li class="nav-item" role="presentation">
<button type="button" @click="setActiveTabPan('contacto')" :class="{ 'active': activeTabPan === 'contacto' }" class="nav-link waves-effect" role="tab" data-bs-toggle="tab" data-bs-target="#navs-left-contacto" aria-controls="navs-left-contacto">
<i class="ti ti-address-book pr-2"></i> Contacto
@ -158,14 +158,6 @@
Es proveedor
</x-checkbox-v>
</div>
<div class="mb-6">
<x-checkbox-v value="{{ old('is_user', $is_user) }}"
name='is_user'
wire:model='is_user'
parent_class='form-switch'>
Es usuario
</x-checkbox-v>
</div>
<div class="row pricelist-div mb-3">
<div class="col-lg-12">
<label for="pricelist_id" class="form-label">Lista de precios</label>
@ -616,16 +608,13 @@
@this.set('is_prospect', false, false);
@this.set('is_customer', true, false);
@this.set('is_provider', false, false);
@this.set('is_user', false, false);
@this.set('enable_credit', false, false);
$("#is_prospect").prop("disabled", true);
$("#is_provider").prop("disabled", true);
$("#is_user").prop("disabled", true);
}else{
$("#is_prospect").prop("disabled", false);
$("#is_provider").prop("disabled", false);
$("#is_user").prop("disabled", false);
}
if (is_prospect || is_customer) {
@ -1643,7 +1632,7 @@
$(document).ready(function() {
$("#pdf-dropzone")
.dropzone({
url: '{{ route('admin.crm.contacts.extraer-datos-pdf-constancia') }}',
url: '{{ route('admin.crm.contacts.extract-data-pdf-certificate') }}',
paramName: "file",
maxFiles: 1,
acceptedFiles: '.pdf',

View File

@ -0,0 +1,41 @@
<div>
<div id="app-description-settings-card" class="form-custom-listener mb-4">
<x-vuexy-admin::card.basic title="Datos de la aplicación" class="mb-4">
<x-vuexy-admin::form.input
label="Titulo de la aplicación"
model="app_name"
placeholder="Nombre corto" />
<x-vuexy-admin::form.input
label="Titulo del sitio"
model="title"
placeholder="Titulo del sitio" />
<x-vuexy-admin::form.textarea
label="Descripción del sitio"
model="description"
placeholder="Descripción del sitio" />
</x-vuexy-admin::card.basic>
<div class="row">
<div class="col-lg-12 text-end">
<x-vuexy-admin::button.basic
variant="primary"
size="sm"
icon="ti ti-device-floppy"
label="Guardar cambios"
disabled
wire:click="save"
class="btn-save"
waves />
<x-vuexy-admin::button.basic
variant="secondary"
size="sm"
icon="ti ti-rotate-2"
label="Cancelar"
disabled
wire:click="resetForm"
class="btn-cancel"
waves />
</div>
</div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
</div>

View File

@ -0,0 +1,84 @@
<div>
<div id="app-favicon-settings-card" class="mb-4">
<x-vuexy-admin::card.basic title="Favicon" class="mb-2">
<x-vuexy-admin::form.input
type="file"
label="Icono de navegador"
model="upload_image_favicon"
accept="image/*" />
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-16x16 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_16x16) }}">
</div>
<span class="text-muted mt-1">Navegadores web (16x16)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-76x76 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_76x76) }}">
</div>
<span class="text-muted mt-1">iPad sin Retina (76x76)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-120x120 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_120x120) }}">
</div>
<span class="text-muted mt-1">iPhone (120x120)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-152x152 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_152x152) }}">
</div>
<span class="text-muted mt-1">iPad (152x152)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-180x180 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_180x180) }}">
</div>
<span class="text-muted mt-1">iPhone con Retina HD (180x180)</span>
</div>
</div>
<div class="text-center flex flex-col items-center">
<div class="mb-3 text-center d-flex flex-column align-items-center">
<div class="image-wrapper-192x192 d-flex justify-content-center align-items-center">
<img src="{{ $upload_image_favicon ? $upload_image_favicon->temporaryUrl() : asset('storage/' . $admin_favicon_192x192) }}">
</div>
<span class="text-muted mt-1">Android y otros dispositivos móviles (192x192)</span>
</div>
</div>
</div>
</x-vuexy-admin::card.basic>
<div class="row">
<div class="col-lg-12 text-end">
<x-vuexy-admin::button.basic
variant="primary"
size="sm"
icon="ti ti-device-floppy"
label="Guardar cambios"
wire:click="save"
:disabled="$upload_image_favicon === null"
class="btn-save mt-2 mr-2"
waves />
<x-vuexy-admin::button.basic
variant="secondary"
size="sm"
icon="ti ti-rotate-2"
label="Cancelar"
wire:click="resetForm"
:disabled="$upload_image_favicon === null"
class="btn-cancel mt-2 mr-2"
waves />
</div>
</div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
</div>

View File

@ -0,0 +1,69 @@
<div x-data>
<div id="interface-settings-card" class="form-custom-listener mb-4">
<div class="row">
<div class="col-md-6">
{{-- Tema --}}
<x-vuexy-admin::card.basic title="Ajustes de tema" class="mb-6">
<x-vuexy-admin::form.select :uid="$uniqueId" model="vuexy_myTheme" label="Tema" :options="['theme-default' => 'Tema predeterminado', 'theme-bordered' => 'Tema bordeado', 'theme-semi-dark' => 'Tema semi-oscuro']" />
<x-vuexy-admin::form.select :uid="$uniqueId" model="vuexy_myStyle" label="Estilo" :options="['light' => 'Claro', 'dark' => 'Oscuro', 'system' => 'Modo del sistema']" />
</x-vuexy-admin::card.basic>
{{-- Diseño --}}
<x-vuexy-admin::card.basic title="Ajustes de diseño" class="mb-6">
<x-vuexy-admin::form.select :uid="$uniqueId" model="vuexy_authViewMode" label="Modo de vista de autenticación" :options="['cover' => 'Pantalla completa', 'basic' => 'Básico']" />
<x-vuexy-admin::form.select :uid="$uniqueId" model="vuexy_contentLayout" label="Ancho predeterminado" :options="['compact' => 'Compacto', 'wide' => 'Ancho completo']" />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="vuexy_footerFixed" label="Fijar pie de página" switch />
</x-vuexy-admin::card.basic>
</div>
<div class="col-md-6">
{{-- Ajustes de menú y barra superior --}}
<x-vuexy-admin::card.basic title="Ajustes menú y barra superior" class="mb-6">
{{-- Diseño (Layout) --}}
<x-vuexy-admin::form.select :uid="$uniqueId" model="vuexy_myLayout" label="Diseño de menú" :options="['vertical' => 'Vertical', 'horizontal' => 'Horizontal']" />
{{-- Horizontal layout --}}
<div x-show="$wire.vuexy_myLayout === 'horizontal'" x-cloak x-transition>
<x-vuexy-admin::form.select :uid="$uniqueId" model="vuexy_headerType" label="Tipo de barra superior" :options="['static' => 'Estático', 'fixed' => 'Fijo']" />
</div>
{{-- Vertical layout --}}
<div x-show="$wire.vuexy_myLayout === 'vertical'" x-cloak x-transition>
<x-vuexy-admin::form.select :uid="$uniqueId" model="vuexy_navbarType" label="Tipo de barra de navegación" :options="['sticky' => 'Fija', 'static' => 'Estática', 'hidden' => 'Oculta']" />
</div>
{{-- Personalizador activo --}}
<div x-show="$wire.vuexy_myLayout === 'horizontal'" x-cloak x-transition>
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="vuexy_showDropdownOnHover" label="Mostrar desplegable al pasar el mouse" switch />
</div>
{{-- Opciones para diseño vertical --}}
<div x-show="$wire.vuexy_myLayout === 'vertical'" x-cloak x-transition>
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="vuexy_menuFixed" label="Menú fijo" switch />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="vuexy_menuCollapsed" label="Menú colapsado" switch />
</div>
</x-vuexy-admin::card.basic>
{{-- Atajos --}}
<div x-show="$wire.vuexy_myLayout === 'horizontal' || $wire.vuexy_navbarType !== 'hidden'" x-cloak x-transition>
<x-vuexy-admin::card.basic title="Atajos" class="mb-6">
<x-vuexy-admin::form.input type="number" :uid="$uniqueId" model="vuexy_maxQuickLinks" label="Máximo de enlaces rápidos" min="2" max="20" helperText="Selecciona un valor entre 2 y 20." />
</x-vuexy-admin::card.basic>
</div>
{{-- Personalizador de plantilla --}}
<x-vuexy-admin::card.basic title="Personalizador de plantilla" class="mb-6">
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="vuexy_hasCustomizer" label="Habilitar personalizador de plantilla" switch />
<div x-show="$wire.vuexy_hasCustomizer" x-cloak x-transition>
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="vuexy_displayCustomizer" label="Mostrar personalizador de plantilla" switch />
</div>
</x-vuexy-admin::card.basic>
</div>
</div>
{{-- Acciones --}}
<div class="row">
<div class="col-lg-12 text-end">
<x-vuexy-admin::button.basic wire:click="save" disabled variant="primary" icon="ti ti-check" label="Aplicar cambios" class="btn-save mb-2 mx-2" size="sm" waves />
<x-vuexy-admin::button.basic wire:click="resetForm" disabled variant="secondary" icon="ti ti-rotate-2" label="Cancelar" class="btn-cancel mb-2 mx-2" size="sm" waves />
<x-vuexy-admin::button.basic wire:click="clearCustomConfig" variant="success" icon="ti ti-adjustments-cog" label="Restaurar valores predeterminados" class="btn-reset mb-2 mx-2" size="sm" waves />
</div>
</div>
{{-- Notificaciones --}}
<div class="notification-container pt-4" wire:ignore></div>
</div>
</div>

View File

@ -0,0 +1,39 @@
<div>
<div id="logo-on-dark-bg-settings-card" class="mb-4">
<x-vuexy-admin::card.basic title="Logotipo sobre fondo oscuro" class="mb-2">
<x-vuexy-admin::form.input
type="file"
label="Logotipo sobre fondo oscuro"
model="upload_image_logo_dark"
accept="image/*" />
<div class="mb-3 text-center align-items-center">
<div class="justify-content-center align-items-center bg-slate-800 p-4">
<img src="{{ $upload_image_logo_dark ? $upload_image_logo_dark->temporaryUrl() : asset('storage/' . $admin_image_logo_dark) }}">
</div>
</div>
</x-vuexy-admin::card.basic>
<div class="row">
<div class="col-lg-12 text-end">
<x-vuexy-admin::button.basic
variant="primary"
size="sm"
icon="ti ti-device-floppy"
disabled="{{ $upload_image_logo_dark === null }}"
label="Guardar cambios"
wire:click="save"
class="btn-save mt-2 mr-2"
waves />
<x-vuexy-admin::button.basic
variant="secondary"
size="sm"
icon="ti ti-rotate-2"
disabled="{{ $upload_image_logo_dark === null }}"
label="Cancelar"
wire:click="resetForm"
class="btn-cancel mt-2 mr-2"
waves />
</div>
</div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
</div>

View File

@ -0,0 +1,39 @@
<div>
<div id="logo-on-light-bg-settings-card" class="mb-4">
<x-vuexy-admin::card.basic title="Logotipo sobre fondo claro" class="mb-2">
<x-vuexy-admin::form.input
type="file"
label="Logotipo sobre fondo claro"
model="upload_image_logo"
accept="image/*" />
<div class="mb-3 text-center align-items-center">
<div class="justify-content-center align-items-center bg-slate-100 p-4">
<img src="{{ $upload_image_logo ? $upload_image_logo->temporaryUrl() : asset('storage/' . $admin_image_logo) }}">
</div>
</div>
</x-vuexy-admin::card.basic>
<div class="row">
<div class="col-lg-12 text-end">
<x-vuexy-admin::button.basic
variant="primary"
size="sm"
icon="ti ti-device-floppy"
disabled="{{ $upload_image_logo === null }}"
label="Guardar cambios"
wire:click="save"
class="btn-save mt-2 mr-2"
waves />
<x-vuexy-admin::button.basic
variant="secondary"
size="sm"
icon="ti ti-rotate-2"
disabled="{{ $upload_image_logo === null }}"
label="Cancelar"
wire:click="resetForm"
class="btn-cancel mt-2 mr-2"
waves />
</div>
</div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
</div>

View File

@ -0,0 +1,53 @@
@php
/**
* Vista Blade para mostrar los accesos rápidos.
* Compatible con Vuexy Admin y modo oscuro.
*/
@endphp
<div class="p-6 space-y-8">
@foreach ($quickAccessItems as $category)
<div class="mb-8">
<!-- Título de categoría con icono -->
<div class="d-flex align-items-center mb-3">
<i class="{{ $category['icon'] }} text-3xl text-primary"></i>
<h5 class="mb-0 ms-2 text-dark dark:text-white">{{ $category['title'] }}</h5>
</div>
<!-- Descripción de categoría -->
@if (!empty($category['description']))
<p class="text-muted">
{{ $category['description'] }}
</p>
@endif
<!-- Grid de accesos rápidos en formato de Cards -->
@if (!empty($category['submenu']))
<div class="row row-cols-2 row-cols-md-4 row-cols-lg-5 g-4">
@foreach ($category['submenu'] as $item)
<div class="col">
<a href="{{ $item['url'] }}" class="text-decoration-none">
<div class="card border-0 shadow-sm hover:shadow-lg transition-all duration-300">
<div class="card-body d-flex flex-column align-items-center justify-content-center text-center p-4">
<!-- Ícono -->
<i class="{{ $item['icon'] }} text-4xl text-primary mb-2"></i>
<!-- Título -->
<h6 class="mb-0 text-dark dark:text-light fw-semibold">
{{ $item['title'] }}
</h6>
</div>
</div>
</a>
</div>
@endforeach
</div>
@else
<p class="text-muted fst-italic">
No hay accesos rápidos en esta categoría.
</p>
@endif
</div>
@endforeach
</div>

View File

@ -2,7 +2,7 @@
changeSmtpSettings: @entangle('change_smtp_settings'),
saveButtonDisabled: @entangle('save_button_disabled'),
}">
<form id="mail-smtp-settings-card">
<form id="sendmail-settings-card">
<div class="card mb-6">
<h5 class="card-header">Servidor saliente de correo electrónico</h5>
<div class="card-body">
@ -80,7 +80,7 @@
</div>
</div>
{{-- Notifications --}}
<div class="notification-container" wire:ignore></div>
<div class="notification-container pt-4" wire:ignore></div>
</div>
</form>
</div>