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

@ -106,7 +106,8 @@ class BootstrapTableManager {
* Carga los formatters dinámicamente
*/
async loadFormatters() {
const formattersModules = import.meta.glob('../../../../../**/resources/assets/js/bootstrap-table/*Formatters.js');
//const formattersModules = import.meta.glob('../../../../../**/resources/assets/js/bootstrap-table/*Formatters.js');
const formattersModules = import.meta.glob('/vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/*Formatters.js');
const formatterPromises = Object.entries(formattersModules).map(async ([path, importer]) => {
const module = await importer();

View File

@ -1,5 +1,5 @@
import { booleanStatusCatalog, statusIntBadgeBgCatalogCss, statusIntBadgeBgCatalog } from './globalConfig';
import {routes} from '../../../../../laravel-vuexy-admin/resources/assets/js/bootstrap-table/globalConfig.js';
import {routes} from '@vuexy-admin/bootstrap-table/globalConfig.js';
export const userActionFormatter = (value, row, index) => {
if (!row.id) return '';

View File

@ -10,7 +10,7 @@
// JS global variables
window.config = {
colors: {
primary: '#7367f0',
primary: '#155dfc',
secondary: '#808390',
success: '#28c76f',
info: '#00bad1',
@ -27,7 +27,7 @@ window.config = {
borderColor: '#e6e6e8'
},
colors_label: {
primary: '#7367f029',
primary: '#155dfc29',
secondary: '#a8aaae29',
success: '#28c76f29',
info: '#00cfe829',

View File

@ -0,0 +1,79 @@
/**
* Previene interacción con el elemento.
* @param {Event} event - El evento de clic.
*/
const preventInteraction = (event) => event.preventDefault();
/**
* Habilita o deshabilita un select con Select2.
* @param {HTMLElement} selectElement - El select afectado.
* @param {boolean} disabled - Si debe ser deshabilitado.
*/
const toggleSelect2Disabled = (selectElement, disabled) => {
selectElement.disabled = disabled;
selectElement.dispatchEvent(new Event('change', { bubbles: true }));
};
/**
* Aplica modo solo lectura a un select estándar o Select2.
* @param {HTMLElement} select - El select a modificar.
* @param {boolean} readonly - Si debe estar en modo solo lectura.
*/
const setSelectReadonly = (select, readonly) => {
select.setAttribute('readonly-mode', readonly);
select.style.pointerEvents = readonly ? 'none' : '';
select.tabIndex = readonly ? -1 : '';
if (select.classList.contains('select2-hidden-accessible')) {
toggleSelect2Disabled(select, readonly);
}
};
/**
* Aplica modo solo lectura a un checkbox o radio.
* @param {HTMLElement} checkbox - El input a modificar.
* @param {boolean} readonly - Si debe ser solo lectura.
*/
const setCheckboxReadonly = (checkbox, readonly) => {
checkbox.setAttribute('readonly-mode', readonly);
checkbox.style.pointerEvents = readonly ? 'none' : '';
checkbox[readonly ? 'addEventListener' : 'removeEventListener']('click', preventInteraction);
};
/**
* Deshabilita o pone en modo de solo lectura los campos del formulario.
* @param {string} formSelector - Selector del formulario a deshabilitar.
*/
export default function disableForm(formSelector) {
const form = document.querySelector(formSelector);
if (!form) {
console.warn(`Formulario no encontrado con el selector: ${formSelector}`);
return;
}
const inputs = form.querySelectorAll('input, textarea, select');
inputs.forEach((el) => {
if (!el.classList.contains('data-always-enabled')) {
switch (el.tagName) {
case 'SELECT':
if (el.classList.contains('select2')) {
toggleSelect2Disabled(el, true);
} else {
setSelectReadonly(el, true);
}
break;
case 'INPUT':
if (['checkbox', 'radio'].includes(el.type)) {
setCheckboxReadonly(el, true);
} else {
el.readOnly = true;
}
break;
case 'TEXTAREA':
el.readOnly = true;
break;
}
}
});
}

View File

@ -155,7 +155,19 @@ export default class FormCustomListener {
this.setButtonLoadingState(saveButton, true); // Poner en estado de carga al botón anfitrión
// Enviar la solicitud de Livewire correspondiente al enviar el formulario
Livewire.dispatch(this.config.dispatchOnSubmit);
const componentEl = form.closest('[wire\\:id]');
const componentId = componentEl?.getAttribute('wire:id');
if (componentId) {
const component = Livewire.find(componentId);
if (component) {
component.call(this.config.dispatchOnSubmit);
} else {
console.warn('No se encontró el componente Livewire.');
}
} else {
console.warn('No se pudo encontrar wire:id para ejecutar el método Livewire.');
}
}
/**
@ -188,7 +200,10 @@ export default class FormCustomListener {
*/
toggleButtonsState(buttons, isEnabled) {
buttons.forEach(button => {
if (button) button.disabled = !isEnabled;
if (button){
button.disabled = !isEnabled;
button.classList.toggle('disabled', !isEnabled);
}
});
}
@ -217,24 +232,57 @@ export default class FormCustomListener {
*/
initializeValidation(form) {
if (this.config.validationConfig) {
this.formValidationInstance = FormValidation.formValidation(form, this.config.validationConfig).on(
'core.form.valid',
() => this.handleFormValid(form)
);
this.formValidationInstance = FormValidation.formValidation(
form,
this.config.validationConfig
).on('core.form.valid', () => this.handleFormValid(form));
// Aplicamos el fix después de un pequeño delay
setTimeout(() => {
this.fixValidationMessagePosition(form);
}, 100); // Lo suficiente para esperar a que FV inserte los mensajes
}
}
/**
* Mueve los mensajes de error fuera del input-group para evitar romper el diseño
*/
fixValidationMessagePosition(form) {
const groups = form.querySelectorAll('.input-group.has-validation');
groups.forEach(group => {
const errorContainer = group.querySelector('.fv-plugins-message-container');
if (errorContainer) {
// Evita duplicados
if (errorContainer.classList.contains('moved')) return;
// Crear un contenedor si no existe
let target = group.parentElement.querySelector('.fv-message');
if (!target) {
target = document.createElement('div');
target.className = 'fv-message invalid-feedback';
group.parentElement.appendChild(target);
}
target.appendChild(errorContainer);
errorContainer.classList.add('moved'); // Marcar como ya movido
}
});
}
reloadValidation() {
const form = document.querySelector(this.config.formSelector);
// Verificar si el formulario existe y si la validación está inicializada
if (form && this.formValidationInstance) {
try {
// En lugar de destruir la validación, simplemente reiniciamos la validación.
this.formValidationInstance.resetForm(); // Resetear el formulario, limpiando los errores
setTimeout(() => {
this.formValidationInstance.resetForm(); // Limpiar errores
this.initializeValidation(form); // Reinicializar
// Reinicializar la validación con la configuración actual
this.initializeValidation(form);
// 🔁 Reconectar eventos de inputs y botones
this.initFormEvents(form);
}, 1);
} catch (error) {
console.error('Error al reiniciar la validación:', error);
}
@ -242,4 +290,5 @@ export default class FormCustomListener {
console.warn('Formulario no encontrado o instancia de validación no disponible.');
}
}
}

View File

@ -0,0 +1,10 @@
window.createSlug = function(string) {
return string
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-z0-9\s-]/g, '')
.trim()
.replace(/\s+/g, '-')
.replace(/-+/g, '-');
};

View File

@ -0,0 +1,27 @@
/**
* Registra un hook Livewire solo una vez por componente.
* @param {string} hookName - Nombre del hook Livewire (por ejemplo, "morphed").
* @param {string} componentName - Nombre exacto del componente Livewire.
* @param {function} callback - Función que se ejecutará una vez por hook+componente.
*/
export default function registerLivewireHookOnce(hookName, componentName, callback) {
if (!hookName || !componentName || typeof callback !== 'function') {
console.warn('[registerLivewireHookOnce] Parámetros inválidos.');
return;
}
// Clave única para este hook+componente
const safeName = componentName.replace(/[^a-zA-Z0-9]/g, '_');
const key = `__hook_${hookName}_${safeName}`;
if (!window[key]) {
window[key] = true;
Livewire.hook(hookName, ({ component }) => {
if (component.name === componentName) {
// console.info(`[Livewire Hook Triggered] ${hookName} for ${component.name}`);
callback(component);
}
});
}
}

View File

@ -1,133 +0,0 @@
import './../../vendor/libs/leaflet/leaflet'
export const LeafletMapHelper = (() => {
let mapInstance, markerInstance;
const DEFAULT_COORDS = [19.4326, -99.1332]; // Coordenadas de CDMX por defecto
// Valida coordenadas
const isValidCoordinate = (lat, lng) => {
return lat && !isNaN(lat) && lat >= -90 && lat <= 90 && lat !== 0 &&
lng && !isNaN(lng) && lng >= -180 && lng <= 180 && lng !== 0;
};
// Crea opciones del mapa según el modo
const getMapOptions = (mode) => ({
scrollWheelZoom: mode !== 'delete',
dragging: mode !== 'delete',
doubleClickZoom: mode !== 'delete',
boxZoom: mode !== 'delete',
keyboard: mode !== 'delete',
zoomControl: mode !== 'delete',
touchZoom: mode !== 'delete'
});
// Destruir el mapa existente
const destroyMap = () => {
if (mapInstance) {
mapInstance.off();
mapInstance.remove();
mapInstance = null;
}
removeMarker();
};
// Crear marcador en el mapa
const createMarker = (lat, lng, draggable = false, onDragEnd) => {
if (isValidCoordinate(lat, lng)) {
markerInstance = L.marker([lat, lng], { draggable }).addTo(mapInstance)
.bindPopup('<b>Ubicación seleccionada</b>').openPopup();
if (draggable && onDragEnd) {
markerInstance.on('dragend', (e) => {
const { lat, lng } = e.target.getLatLng();
onDragEnd(lat, lng);
});
}
}
};
// Eliminar marcador
const removeMarker = () => {
if (markerInstance) {
markerInstance.remove();
markerInstance = null;
}
};
// Actualizar coordenadas en formulario
const updateCoordinates = (lat, lng, latSelector, lngSelector, livewireInstance) => {
const latInput = document.querySelector(latSelector);
const lngInput = document.querySelector(lngSelector);
if (!latInput || !lngInput) {
console.warn(`⚠️ No se encontró el elemento del DOM para latitud (${latSelector}) o longitud (${lngSelector})`);
return;
}
latInput.value = lat ? lat.toFixed(6) : '';
lngInput.value = lng ? lng.toFixed(6) : '';
if (livewireInstance) {
livewireInstance.lat = lat ? lat.toFixed(6) : null;
livewireInstance.lng = lng ? lng.toFixed(6) : null;
}
};
// Inicializar el mapa
const initializeMap = (locationInputs, mode = 'create', livewireInstance = null) => {
const mapElement = document.getElementById(locationInputs.mapId);
if (!mapElement) {
console.error(`❌ No se encontró el contenedor del mapa con ID: ${locationInputs.mapId}`);
return;
}
let latElement = document.querySelector(locationInputs.lat);
let lngElement = document.querySelector(locationInputs.lng);
if (!latElement || !lngElement) {
console.error(`❌ No se encontraron los campos de latitud (${locationInputs.lat}) o longitud (${locationInputs.lng})`);
return;
}
let lat = parseFloat(latElement.value);
let lng = parseFloat(lngElement.value);
const mapCoords = isValidCoordinate(lat, lng) ? [lat, lng] : DEFAULT_COORDS;
const zoomLevel = isValidCoordinate(lat, lng) ? 16 : 13;
if (mapInstance) destroyMap();
mapInstance = L.map(locationInputs.mapId, getMapOptions(mode)).setView(mapCoords, zoomLevel);
L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(mapInstance);
if (mode !== 'create') createMarker(lat, lng, mode === 'edit', (lat, lng) => updateCoordinates(lat, lng, locationInputs.lat, locationInputs.lng, livewireInstance));
if (mode !== 'delete') {
mapInstance.on('click', (e) => {
const { lat, lng } = e.latlng;
removeMarker();
createMarker(lat, lng, true, (lat, lng) => updateCoordinates(lat, lng, locationInputs.lat, locationInputs.lng, livewireInstance));
updateCoordinates(lat, lng, locationInputs.lat, locationInputs.lng, livewireInstance);
});
}
/*
const btnClearElement = document.querySelector(locationInputs.btnClear);
if(!btnClearElement){
console.error(`❌ No se encontró el botón de limpiar con ID: ${locationInputs.btnClear}`);return;
}
*/
};
return {
initializeMap,
clearCoordinates: () => {
removeMarker();
},
};
})();
window.LeafletMapHelper = LeafletMapHelper;

View File

@ -1,12 +0,0 @@
export class LocationIQSearchHelper {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://us1.locationiq.com/v1/search.php';
}
async searchAddress(query) {
const response = await fetch(`${this.baseUrl}?key=${this.apiKey}&q=${query}&format=json`);
if (!response.ok) throw new Error('Error al buscar la dirección');
return await response.json();
}
}

View File

@ -39,7 +39,7 @@ $grays: (
// scss-docs-start color-variables
$blue: #007bff !default;
$indigo: #6610f2 !default;
$purple: #7367f0 !default;
$purple: #64748b !default;
$pink: #e83e8c !default;
$red: #ff4c51 !default;
$orange: #fd7e14 !default;

View File

@ -35,7 +35,7 @@ $grays: (
// scss-docs-start color-variables
$blue: #007bff !default;
$indigo: #6610f2 !default;
$purple: #7367f0 !default;
$purple: #64748b !default;
$pink: #e83e8c !default;
$red: #ff4c51 !default;
$orange: #fd7e14 !default;

View File

@ -2,3 +2,5 @@
@import 'bootstrap-extended-dark';
@import 'components-dark';
@import 'colors-dark';
$primary-color: #4a8c08;

View File

@ -2,3 +2,5 @@
@import 'bootstrap-extended';
@import 'components';
@import 'colors';
$primary-color: #4a8c08;

View File

@ -4,7 +4,7 @@
@import './_theme/pages';
@import './_theme/_theme';
$primary-color: #7367f0;
$primary-color: #155dfc;
body {
background: $card-bg;

View File

@ -4,7 +4,7 @@
@import './_theme/pages';
@import './_theme/_theme';
$primary-color: #7367f0;
$primary-color: #155dfc;
$body-bg: #f8f7fa;
body {

View File

@ -4,7 +4,7 @@
@import './_theme/pages';
@import './_theme/_theme';
$primary-color: #7367f0;
$primary-color: #155dfc;
body {
background: $body-bg;

View File

@ -4,7 +4,7 @@
@import './_theme/pages';
@import './_theme/_theme';
$primary-color: #7367f0;
$primary-color: #155dfc;
$body-bg: #f8f7fa;
body {

View File

@ -4,7 +4,7 @@
@import './_theme/pages';
@import './_theme/_theme';
$primary-color: #7367f0;
$primary-color: #155dfc;
body {
background: $body-bg;

View File

@ -4,7 +4,7 @@
@import './_theme/pages';
@import './_theme/_theme';
$primary-color: #7367f0;
$primary-color: #155dfc;
$body-bg: #f8f7fa;
body {

View File

@ -1,83 +0,0 @@
/**
* Deshabilita o pone en modo de solo lectura los campos del formulario.
* @param {string} formSelector - Selector del formulario a deshabilitar.
*/
const disableStoreForm = (formSelector) => {
const form = document.querySelector(formSelector);
if (!form) {
console.warn(`Formulario no encontrado con el selector: ${formSelector}`);
return;
}
/**
* Habilita o deshabilita un select con Select2.
* @param {HTMLElement} selectElement - El select afectado.
* @param {boolean} disabled - Si debe ser deshabilitado.
*/
const toggleSelect2Disabled = (selectElement, disabled) => {
selectElement.disabled = disabled;
selectElement.dispatchEvent(new Event('change', { bubbles: true }));
};
/**
* Aplica modo solo lectura a un select estándar o Select2.
* @param {HTMLElement} select - El select a modificar.
* @param {boolean} readonly - Si debe estar en modo solo lectura.
*/
const setSelectReadonly = (select, readonly) => {
select.setAttribute('readonly-mode', readonly);
select.style.pointerEvents = readonly ? 'none' : '';
select.tabIndex = readonly ? -1 : '';
if (select.classList.contains('select2-hidden-accessible')) {
toggleSelect2Disabled(select, readonly);
}
};
/**
* Aplica modo solo lectura a un checkbox o radio.
* @param {HTMLElement} checkbox - El input a modificar.
* @param {boolean} readonly - Si debe ser solo lectura.
*/
const setCheckboxReadonly = (checkbox, readonly) => {
checkbox.setAttribute('readonly-mode', readonly);
checkbox.style.pointerEvents = readonly ? 'none' : '';
checkbox[readonly ? 'addEventListener' : 'removeEventListener']('click', preventInteraction);
};
/**
* Previene interacción con el elemento.
* @param {Event} event - El evento de clic.
*/
const preventInteraction = (event) => event.preventDefault();
// Obtener todos los inputs del formulario
const inputs = form.querySelectorAll('input, textarea, select');
inputs.forEach((el) => {
if (!el.classList.contains('data-always-enabled')) {
switch (el.tagName) {
case 'SELECT':
if (el.classList.contains('select2')) {
toggleSelect2Disabled(el, true);
} else {
setSelectReadonly(el, true);
}
break;
case 'INPUT':
if (['checkbox', 'radio'].includes(el.type)) {
setCheckboxReadonly(el, true);
} else {
el.readOnly = true;
}
break;
case 'TEXTAREA':
el.readOnly = true;
break;
}
}
});
};
// Hacer la función accesible globalmente
window.disableStoreForm = disableStoreForm;

View File

@ -4,24 +4,3 @@ import FormCustomListener from '../../assets/js/forms/formCustomListener';
new FormCustomListener({
buttonSelectors: ['.btn-save', '.btn-cancel', '.btn-reset'] // Selectores para botones
});
Livewire.on('clearLocalStoregeTemplateCustomizer', event => {
const _deleteCookie = name => {
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
};
const pattern = 'templateCustomizer-';
// Iterar sobre todas las claves en localStorage
Object.keys(localStorage).forEach(key => {
if (key.startsWith(pattern)) {
localStorage.removeItem(key);
}
});
_deleteCookie('admin-mode');
_deleteCookie('admin-colorPref');
_deleteCookie('colorPref');
_deleteCookie('theme');
_deleteCookie('direction');
});

View File

@ -7,7 +7,7 @@ window.senderResponseForm = new SenderResponseForm();
Livewire.hook('morphed', ({ component }) => {
switch (component.name) {
case 'mail-smtp-settings':
case 'sendmail-settings':
if (window.smtpSettingsForm) {
window.smtpSettingsForm.reload(); // Recarga el formulario sin destruir la instancia
}

View File

@ -1,7 +1,7 @@
export default class SmtpSettingsForm {
constructor(config = {}) {
const defaultConfig = {
formSmtpSettingsSelector: '#mail-smtp-settings-card',
formSmtpSettingsSelector: '#sendmail-settings-card',
changeSmtpSettingsId: 'change_smtp_settings',
testSmtpConnectionButtonId: 'test_smtp_connection_button',
saveSmtpConnectionButtonId: 'save_smtp_connection_button',

View File

@ -2,6 +2,10 @@
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
[x-cloak] {
display: none !important;
}
.menu-horizontal-wrapper > .menu-inner > .menu-item:last-child {
padding-right: 70px;
}

View File

@ -85,7 +85,7 @@ $authentication-1-inner-max-width: 460px !default;
position: absolute;
top: -35px;
left: -45px;
background-image: url("data:image/svg+xml,%3Csvg width='239' height='234' viewBox='0 0 239 234' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='88.5605' y='0.700195' width='149' height='149' rx='19.5' stroke='%237367F0' stroke-opacity='0.16'/%3E%3Crect x='0.621094' y='33.761' width='200' height='200' rx='10' fill='%237367F0' fill-opacity='0.08'/%3E%3C/svg%3E%0A");
background-image: url("data:image/svg+xml,%3Csvg width='239' height='234' viewBox='0 0 239 234' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='88.5605' y='0.700195' width='149' height='149' rx='19.5' stroke='%23155dfc' stroke-opacity='0.16'/%3E%3Crect x='0.621094' y='33.761' width='200' height='200' rx='10' fill='%23155dfc' fill-opacity='0.08'/%3E%3C/svg%3E%0A");
}
&:after {
@include light.media-breakpoint-down(sm) {
@ -98,7 +98,7 @@ $authentication-1-inner-max-width: 460px !default;
z-index: -1;
bottom: -30px;
right: -56px;
background-image: url("data:image/svg+xml,%3Csvg width='181' height='181' viewBox='0 0 181 181' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.30469' y='1.44312' width='178' height='178' rx='19' stroke='%237367F0' stroke-opacity='0.16' stroke-width='2' stroke-dasharray='8 8'/%3E%3Crect x='22.8047' y='22.9431' width='135' height='135' rx='10' fill='%237367F0' fill-opacity='0.08'/%3E%3C/svg%3E");
background-image: url("data:image/svg+xml,%3Csvg width='181' height='181' viewBox='0 0 181 181' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.30469' y='1.44312' width='178' height='178' rx='19' stroke='%23155dfc' stroke-opacity='0.16' stroke-width='2' stroke-dasharray='8 8'/%3E%3Crect x='22.8047' y='22.9431' width='135' height='135' rx='10' fill='%23155dfc' fill-opacity='0.08'/%3E%3C/svg%3E");
}
}

View File

@ -1,30 +0,0 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', 'Ajustes generales')
@push('page-script')
@vite('vendor/koneko/laravel-vuexy-admin/resources/js/pages/admin-settings-scripts.js')
@endpush
@section('content')
<div class="row">
<div class="col-lg-4">
<!-- App Settings Card -->
<div class="mb-4">
@livewire('application-settings')
</div>
</div>
<div class="col-lg-4">
<!-- General Settings Card -->
<div class="mb-4">
@livewire('general-settings')
</div>
</div>
<div class="col-lg-4">
<!-- Interface Settings Card -->
<div class="mb-4">
@livewire('interface-settings')
</div>
</div>
</div>
@endsection

View File

@ -10,28 +10,28 @@
<div class="row">
<div class="col-md-4">
<div class="mb-6">
@livewire('cache-stats')
@livewire('vuexy-admin::cache-stats')
</div>
<div class="mb-6">
@livewire('session-stats')
@livewire('vuexy-admin::session-stats')
</div>
</div>
<div class="col-md-8">
<div class="mb-6">
@livewire('cache-functions')
@livewire('vuexy-admin::cache-functions')
</div>
<div class="row">
@if($configCache['redisInUse'])
<div class="col-md-6">
<div class="mb-6">
@livewire('redis-stats')
@livewire('vuexy-admin::redis-stats')
</div>
</div>
@endif
@if($configCache['memcachedInUse'])
<div class="col-md-6">
<div class="mb-6">
@livewire('memcached-stats')
@livewire('vuexy-admin::memcached-stats')
</div>
</div>
@endif

View File

@ -0,0 +1,24 @@
@props([
'type' => 'primary', // Tipos: primary, secondary, success, danger, warning, info, dark
'outline' => false,
'dismissible' => false,
'icon' => null,
])
@php
$alertClass = $outline ? "alert-outline-{$type}" : "alert-{$type}";
@endphp
<div class="alert {{ $alertClass }} {{ $dismissible ? 'alert-dismissible' : '' }} d-flex align-items-center" role="alert">
@if ($icon)
<span class="alert-icon rounded me-2">
<i class="{{ $icon }}"></i>
</span>
@endif
<div>
{{ $slot }}
</div>
@if ($dismissible)
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
@endif
</div>

View File

@ -71,7 +71,7 @@
@endphp
{{-- ============================ CHECKBOX CON INPUT GROUP ============================ --}}
<div class="mb-4 {{ $parentClass }}">
<div class="mb-4 {{ $parentClass }} fv-row">
@if ($label)
<label for="{{ $checkboxId }}" class="{{ $labelClass }}">{{ $label }}</label>
@endif

View File

@ -74,7 +74,7 @@
@if ($switch)
{{-- ============================ MODO SWITCH ============================ --}}
<div class="{{ $alignClass }} {{ $inline ? 'd-inline-block' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-2' }}">
<div class="{{ $alignClass }} {{ $inline ? 'd-inline-block' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-2' }} fv-row">
<label class="switch {{ $switchTypeClass }} {{ $switchColorClass }} {{ $sizeClass }} {{ $labelClass }}">
<input
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
@ -110,7 +110,7 @@
@else
{{-- ============================ MODO CHECKBOX ============================ --}}
<div class="form-check {{ $checkColorClass }} {{ $alignClass }} {{ $sizeClass }} {{ $inline ? 'form-check-inline' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }}">
<div class="form-check {{ $checkColorClass }} {{ $alignClass }} {{ $sizeClass }} {{ $inline ? 'form-check-inline' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }} fv-row">
<input
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
{{ $disabled ? 'disabled' : '' }}

View File

@ -24,7 +24,7 @@
: ($image ? "<img src='{$image}' alt='{$title}' class='img-fluid rounded'>" : '');
@endphp
<div class="mb-4 form-check custom-option custom-option-icon {{ $checked ? 'checked' : '' }}">
<div class="mb-4 form-check custom-option custom-option-icon {{ $checked ? 'checked' : '' }} fv-row">
<label class="form-check-label custom-option-content" for="{{ $inputId }}">
<span class="custom-option-body">
{!! $visualContent !!}

View File

@ -8,6 +8,8 @@
'wireSubmit' => false, // Usar wire:submit.prevent
'class' => '', // Clases adicionales para el formulario
'actionPosition' => 'bottom', // Posición de acciones: top, bottom, both, none
'whitOutId' => false, // Excluir el ID del formulario
'whitOutMode' => false, // Excluir el modo del formulario
])
@php
@ -28,8 +30,12 @@
@endphp
<form {{ $attributes->merge($formAttributes) }}>
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="id" />
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="mode" />
@if (!$whitOutId)
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="id" />
@endif
@if (!$whitOutMode)
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="mode" />
@endif
@if ($mode !== 'delete' && in_array($actionPosition, ['top', 'both']))
<div class="notification-container mb-4"></div>
<div class="form-actions mb-4">

View File

@ -1,184 +0,0 @@
@props([
'uid' => uniqid(),
'model' => '',
'label' => '',
'labelClass' => '',
'class' => '',
'align' => 'start',
'size' => '',
'mb-0' => false,
'parentClass' => '',
'prefix' => null,
'suffix' => null,
'icon' => null,
'clickableIcon' => null,
'inline' => false,
'labelCol' => 4,
'inputCol' => 8,
'floatLabel' => false,
'helperText' => '',
'attributes' => new \Illuminate\View\ComponentAttributeBag([]), // Atributos adicionales
])
@php
// Configuración dinámica de atributos y clases CSS
$livewireModel = $attributes->get('wire:model', $model); // Permitir uso directo de wire:model en el atributo
$name = $name ?: $livewireModel; // Si no se proporciona el nombre, toma el nombre del modelo
$inputId = $id ?: ($uid ? $name . '_' . $uid : $name); // ID generado si no se proporciona uno
// Obtener los atributos actuales en un array
$attributesArray = array_merge([
'type' => $type,
'id' => $inputId,
'name' => $name,
]);
// Agregar wire:model solo si existe
if ($livewireModel) {
$attributesArray['wire:model'] = $livewireModel;
}
$attributesArray = array_merge($attributesArray, $attributes->getAttributes());
// Reconstruir el ComponentAttributeBag con los atributos modificados
$inputAttributes = new \Illuminate\View\ComponentAttributeBag($attributesArray);
dump($inputAttributes);
// Manejo de errores de validación
$errorKey = $livewireModel ?: $name;
$errorClass = $errors->has($errorKey) ? 'is-invalid' : '';
// Definir el tamaño del input basado en la clase seleccionada
$sizeClass = $size === 'small' ? 'form-control-sm' : ($size === 'large' ? 'form-control-lg' : '');
// Alineación del texto
$alignClass = match ($align) {
'center' => 'text-center',
'end' => 'text-end',
default => ''
};
// Clases combinadas para el input
$fullClass = trim("form-control $sizeClass $alignClass $errorClass $class");
// Detectar si se necesita usar input-group
$requiresInputGroup = $prefix || $suffix || $icon || $clickableIcon;
@endphp
{{-- Input oculto sin estilos --}}
@if($type === 'hidden')
<input type="hidden" id="{{ $inputId }}" name="{{ $name }}" {{ $livewireModel ? "wire:model=$livewireModel" : '' }}
/>
@elseif($floatLabel)
{{-- Input con etiqueta flotante --}}
<div class="form-floating mb-4 {{ $parentClass }}">
<input
type="{{ $type }}"
id="{{ $inputId }}"
name="{{ $name }}"
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
class="{{ $fullClass }}"
placeholder="{{ $placeholder ?: 'Ingrese ' . strtolower($label) }}"
{{ $step ? "step=$step" : '' }}
{{ $max ? "maxlength=$max" : '' }}
{{ $min ? "minlength=$min" : '' }}
{{ $pattern ? "pattern=$pattern" : '' }}
{{ $disabled ? 'disabled' : '' }}
{{ $multiple ? 'multiple' : '' }} />
<label for="{{ $inputId }}">{{ $label }}</label>
@if ($helperText)
<div class="form-text">{{ $helperText }}</div>
@endif
@if ($errors->has($errorKey))
<span class="text-danger">{{ $errors->first($errorKey) }}</span>
@endif
</div>
@else
{{-- Input con formato clásico --}}
<div class="{{ $inline ? 'row' : '' }} {{ $parentClass }} fv-row mb-4">
@isset($label)
<label for="{{ $inputId }}" class="{{ $inline ? 'col-form-label col-md-' . $labelCol : 'form-label' }} {{ $labelClass }}">{{ $label }}</label>
@endisset
<div class="{{ $inline ? 'col-md-' . $inputCol : '' }}">
@if ($requiresInputGroup)
<div class="input-group input-group-merge">
@if ($prefix)
<span class="input-group-text">{{ $prefix }}</span>
@endif
@if ($icon)
<span class="input-group-text"><i class="{{ $icon }}"></i></span>
@endif
<input
type="{{ $type }}"
id="{{ $inputId }}"
name="{{ $name }}"
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
class="{{ $fullClass }}"
placeholder="{{ $placeholder ?: 'Ingrese ' . strtolower($label) }}"
{{ $step ? "step=$step" : '' }}
{{ $max ? "maxlength=$max" : '' }}
{{ $min ? "minlength=$min" : '' }}
{{ $pattern ? "pattern=$pattern" : '' }}
{{ $disabled ? 'disabled' : '' }}
{{ $multiple ? 'multiple' : '' }} />
@if ($suffix)
<span class="input-group-text">{{ $suffix }}</span>
@endif
@if ($clickableIcon)
<button type="button" class="input-group-text cursor-pointer">
<i class="{{ $clickableIcon }}"></i>
</button>
@endif
</div>
@else
{{-- Input sin prefijo o íconos --}}
<input
type="{{ $type }}"
id="{{ $inputId }}"
name="{{ $name }}"
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
class="{{ $fullClass }}"
placeholder="{{ $placeholder ?: 'Ingrese ' . strtolower($label) }}"
{{ $step ? "step=$step" : '' }}
{{ $max ? "maxlength=$max" : '' }}
{{ $min ? "minlength=$min" : '' }}
{{ $pattern ? "pattern=$pattern" : '' }}
{{ $disabled ? 'disabled' : '' }}
{{ $multiple ? 'multiple' : '' }} />
@endif
@if ($helperText)
<small class="form-text text-muted">{{ $helperText }}</small>
@endif
@if ($errors->has($errorKey))
<span class="text-danger">{{ $errors->first($errorKey) }}</span>
@endif
</div>
</div>
@endif

View File

@ -16,13 +16,18 @@
'mb0' => false, // Remover margen inferior
'parentClass' => '',
// Elementos opcionales antes/después del input
// Elementos de prefijo
'prefix' => null,
'suffix' => null,
'prefixIcon' => null,
'icon' => null, // Alias para prefixIcon
'prefixClickable' => false,
'prefixAction' => null,
// Íconos dentro del input
'icon' => null,
'clickableIcon' => null,
// Elementos de sufijo
'suffix' => null,
'suffixIcon' => null,
'suffixClickable' => false,
'suffixAction' => null,
// Configuración especial
'phoneMode' => false, // "national", "international", "both"
@ -47,6 +52,9 @@
$inputId = $attributes->get('id', $name . '_' . $uid);
$type = $attributes->get('type', 'text');
// Manejar el alias de icon a prefixIcon
$prefixIcon = $prefixIcon ?? $icon;
// **Definir formato de teléfono según `phoneMode`**
if ($phoneMode) {
$type = 'tel';
@ -120,37 +128,68 @@
'id' => $inputId,
'name' => $name,
])->class("form-control $sizeClass $alignClass $errorClass");
// Verificar si se necesita el input-group
$hasAddons = $prefix || $prefixIcon || $suffix || $suffixIcon;
@endphp
{{-- Estructura del Input --}}
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }}">
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} fv-row">
{{-- Etiqueta --}}
@if ($label)
<label for="{{ $inputId }}" class="form-label {{ $labelClass }}">{{ $label }}</label>
@endif
{{-- Input con Prefijos, Sufijos o Íconos --}}
@if ($prefix || $suffix || $icon || $clickableIcon)
{{-- Input con Prefijos o Sufijos --}}
@if ($hasAddons)
<div class="input-group input-group-merge">
@isset($prefix)
<span class="input-group-text">{{ $prefix }}</span>
@endisset
@isset($icon)
<span class="input-group-text"><i class="{{ $icon }}"></i></span>
@endisset
{{-- Prefijo --}}
@if ($prefix || $prefixIcon)
@if ($prefixClickable)
<button type="button" class="input-group-text cursor-pointer" {{ $prefixAction ? "wire:click=$prefixAction" : '' }}>
@if ($prefixIcon)
<i class="{{ $prefixIcon }}"></i>
@endif
@if ($prefix)
{{ $prefix }}
@endif
</button>
@else
<span class="input-group-text">
@if ($prefixIcon)
<i class="{{ $prefixIcon }}"></i>
@endif
@if ($prefix)
{{ $prefix }}
@endif
</span>
@endif
@endif
<input {!! $inputAttributes !!} {{ $livewireModel ? "wire:model=$livewireModel" : '' }} />
@isset($suffix)
<span class="input-group-text">{{ $suffix }}</span>
@endisset
@isset($clickableIcon)
<button type="button" class="input-group-text cursor-pointer">
<i class="{{ $clickableIcon }}"></i>
</button>
@endisset
{{-- Sufijo --}}
@if ($suffix || $suffixIcon)
@if ($suffixClickable)
<button type="button" class="input-group-text cursor-pointer" {{ $suffixAction ? "wire:click=$suffixAction" : '' }}>
@if ($suffixIcon)
<i class="{{ $suffixIcon }}"></i>
@endif
@if ($suffix)
{{ $suffix }}
@endif
</button>
@else
<span class="input-group-text">
@if ($suffixIcon)
<i class="{{ $suffixIcon }}"></i>
@endif
@if ($suffix)
{{ $suffix }}
@endif
</span>
@endif
@endif
</div>
@else
{{-- Input Simple --}}
@ -166,4 +205,4 @@
@if ($hasError)
<span class="text-danger">{{ $errors->first($errorKey) }}</span>
@endif
</div>
</div>

View File

@ -72,7 +72,7 @@
@endphp
{{-- ============================ RADIO BUTTON CON INPUT GROUP ============================ --}}
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }}">
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} fv-row">
@if ($label)
<label for="{{ $radioId }}" class="{{ $labelClass }}">{{ $label }}</label>
@endif
@ -86,7 +86,7 @@
type="radio"
{{ $livewireRadio }}
{{ $disabled ? 'disabled' : '' }}
class="form-check-input mt-0"
class="form-check-input fv-row mt-0"
onchange="toggleRadioInputState('{{ $radioId }}', '{{ $textInputId }}', {{ $focusOnCheck ? 'true' : 'false' }}, {{ $disableOnOffRadio ? 'true' : 'false' }})"
>
</div>

View File

@ -72,7 +72,7 @@
@if ($switch)
{{-- ============================ MODO SWITCH ============================ --}}
<div class="{{ $alignClass }} {{ $inline ? 'd-inline-block' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }}">
<div class="{{ $alignClass }} {{ $inline ? 'd-inline-block' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }} fv-row">
<label class="switch {{ $switchTypeClass }} {{ $switchColorClass }} {{ $sizeClass }} {{ $labelClass }}">
<input
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
@ -102,7 +102,7 @@
@else
{{-- ============================ MODO RADIO ============================ --}}
<div class="{{ $layoutClass }} {{ $radioColorClass }} {{ $alignClass }} {{ $sizeClass }} {{ $inline ? 'form-check-inline' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }}">
<div class="{{ $layoutClass }} {{ $radioColorClass }} {{ $alignClass }} {{ $sizeClass }} {{ $inline ? 'form-check-inline' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }} fv-row">
<input
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
{{ $disabled ? 'disabled' : '' }}

View File

@ -1,86 +0,0 @@
@props([
'uid' => uniqid(),
'id' => '',
'model' => '',
'name' => '',
'label' => '',
'labelClass' => 'form-label',
'placeholder' => '',
'options' => [],
'selected' => null,
'class' => '',
'parentClass' => '',
'multiple' => false,
'disabled' => false,
'prefixLabel' => null,
'suffixLabel' => null,
'buttonBefore' => null,
'buttonAfter' => null,
'inline' => false, // Si es en línea
'labelCol' => 2, // Columnas que ocupa el label (Bootstrap grid)
'inputCol' => 10, // Columnas que ocupa el input (Bootstrap grid)
'helperText' => '', // Texto de ayuda opcional
'select2' => false, // Activar Select2 automáticamente
])
@php
$name = $name ?: $model;
$inputId = $id ?: ($uid ? str_replace('.', '_', $name) . '_' . $uid : $name);
$placeholder = $placeholder ?: 'Seleccione ' . strtolower($label);
$errorClass = $errors->has($model) ? 'is-invalid' : '';
$options = is_array($options) ? collect($options) : $options;
$select2Class = $select2 ? 'select2' : ''; // Agrega la clase select2 si está habilitado
@endphp
<div class="{{ $inline ? 'row' : 'mb-4' }} {{ $parentClass }} fv-row">
@if($label != null)
<label for="{{ $inputId }}" class="{{ $inline ? 'col-md-' . $labelCol : '' }} {{ $labelClass }}">{{ $label }}</label>
@endif
<div class="{{ $inline ? 'col-md-' . $inputCol : '' }}">
<div class="input-group {{ $prefixLabel || $suffixLabel || $buttonBefore || $buttonAfter ? 'input-group-merge' : '' }}">
@if ($buttonBefore)
<button class="btn btn-outline-primary waves-effect" type="button">{{ $buttonBefore }}</button>
@endif
@if ($prefixLabel)
<label class="input-group-text" for="{{ $inputId }}">{{ $prefixLabel }}</label>
@endif
<select
id="{{ $inputId }}"
name="{{ $name }}"
class="form-select {{ $errorClass }} {{ $class }} {{ $select2Class }}"
{{ $multiple ? 'multiple' : '' }}
{{ $disabled ? 'disabled' : '' }}
{{ $model ? "wire:model=$model" : '' }}
{{ $select2 ? 'data-live-search="true"' : '' }}
>
@if (!$multiple && $placeholder)
<option value="">{{ $placeholder }}</option>
@endif
@foreach ($options as $key => $value)
<option value="{{ $key }}" {{ (string) $key === (string) $selected ? 'selected' : '' }}>
{{ $value }}
</option>
@endforeach
</select>
@if ($suffixLabel)
<label class="input-group-text" for="{{ $inputId }}">{{ $suffixLabel }}</label>
@endif
@if ($buttonAfter)
<button class="btn btn-outline-primary waves-effect" type="button">{{ $buttonAfter }}</button>
@endif
</div>
@if ($helperText)
<div class="form-text">{{ $helperText }}</div>
@endif
@if ($errors->has($model))
<span class="text-danger">{{ $errors->first($model) }}</span>
@endif
</div>
</div>

View File

@ -88,7 +88,7 @@
@endphp
{{-- ============================ TEXTAREA ============================ --}}
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} {{ $alignClass }} {{ $floatingClass }}">
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} {{ $alignClass }} {{ $floatingClass }} fv-row">
@if (!$floating && $label)
<label for="{{ $inputId }}" class="{{ $labelClass }}">{{ $label }}</label>
@endif

View File

@ -0,0 +1,20 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', 'Ajustes generales')
@push('page-script')
@vite('vendor/koneko/laravel-vuexy-admin/resources/js/pages/admin-settings-scripts.js')
@endpush
@section('content')
<div class="row">
<div class="col-lg-5">
@livewire('vuexy-admin::app-description-settings')
@livewire('vuexy-admin::app-favicon-settings')
</div>
<div class="col-lg-4">
@livewire('vuexy-admin::logo-on-light-bg-settings')
@livewire('vuexy-admin::logo-on-dark-bg-settings')
</div>
</div>
@endsection

View File

@ -0,0 +1,22 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', 'Configuraciones globales')
@section('vendor-style')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss',
])
@endsection
@push('page-script')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js',
])
@endpush
@section('content')
@livewire('vuexy-admin::global-settings-index')
@livewire('vuexy-admin::global-setting-offcanvas-form')
@endsection

View File

@ -0,0 +1,11 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', 'Ajustes de la interfaz')
@push('page-script')
@vite('vendor/koneko/laravel-vuexy-admin/resources/js/pages/admin-settings-scripts.js')
@endpush
@section('content')
@livewire('vuexy-admin::interface-settings')
@endsection

View File

@ -181,7 +181,7 @@ $navbarDetached = ($navbarDetached ?? '');
<li>
<a class="dropdown-item" href="{{ route('admin.core.user-profile.index') }}">
<i class="ti ti-user-cog me-2 ti-sm"></i>
<span class="align-middle">Configuración de cuenta</span>
<span class="align-middle">Cuenta de usuario</span>
</a>
</li>
@endif

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>

View File

@ -1,7 +1,3 @@
@php
$configData = Helper::appClasses();
@endphp
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('content')

View File

@ -5,45 +5,5 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('content')
<div class="row">
<h4>{{ $_admin['title'] }}</h4>
<p>Para mayor información al respecto consulta la <a href="{{ config('koneko.documentation') }}" target="_blank" rel="noopener noreferrer">documentación</a>.</p>
@php
use Illuminate\Support\Facades\Auth;
// Obtener el usuario autenticado
$user = Auth::user();
echo '<pre>';
if ($user) {
// Imprimir información del usuario
echo "Usuario: {$user->name}\n";
echo "Email: {$user->email}\n\n";
// Obtener todos los roles del usuario
$roles = $user->roles;
// Iterar sobre los roles del usuario
foreach ($roles as $role) {
echo "Rol: {$role->name}\n";
// Obtener todos los permisos del rol
$permissions = $role->permissions;
// Imprimir los permisos del rol
foreach ($permissions as $permission) {
echo " - Permiso: {$permission->name}\n";
}
echo "\n";
}
} else {
echo "Usuario no autenticado\n";
}
echo '</pre>';
@endphp
@livewire('vuexy-admin::quick-access-widget');
@endsection

View File

@ -4,27 +4,26 @@
@section('vendor-style')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/datatables-bs5/datatables.bootstrap5.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/datatables-responsive-bs5/responsive.bootstrap5.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/datatables-buttons-bs5/buttons.bootstrap5.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/@form-validation/form-validation.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss',
])
@endsection
@section('vendor-script')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/datatables-bs5/datatables-bootstrap5.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/@form-validation/popular.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/@form-validation/bootstrap5.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/@form-validation/auto-focus.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js',
])
@endsection
@push('page-script')
@vite('vendor/koneko/laravel-vuexy-admin/resources/js/pages/roles-scripts.js/permissions-scripts.js')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js',
])
@endpush
@section('content')
@livewire('permissions-index')
@livewire('vuexy-admin::permissions-index')
@livewire('vuexy-admin::permission-offcanvas-form')
@endsection

View File

@ -1,38 +1,33 @@
@extends('layouts.vuexy.layoutMaster')
@php
$breadcrumbs = [['link' => 'home', 'name' => 'Home'], ['link' => 'javascript:void(0)', 'name' => 'User'], ['name' => 'Profile']];
@endphp
@section('title', 'Profile')
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', 'Perfil de usuario')
@section('content')
@if (Laravel\Fortify\Features::canUpdateProfileInformation())
<div class="mb-6">
@livewire('profile.update-profile-information-form')
</div>
@endif
@if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::updatePasswords()))
<?php /*
@if (Laravel\Fortify\Features::canUpdateProfileInformation())
<div class="mb-6">
@livewire('profile.update-password-form')
@livewire('vuexy-admin::update-profile-information-form')
</div>
@endif
@endif
@if (Laravel\Fortify\Features::canManageTwoFactorAuthentication())
<div class="mb-6">
@livewire('profile.two-factor-authentication-form')
</div>
@endif
@if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::updatePasswords()))
<div class="mb-6">
@livewire('profile.update-password-form')
</div>
@endif
<div class="mb-6">
@livewire('profile.logout-other-browser-sessions-form')
</div>
@if (Laravel\Fortify\Features::canManageTwoFactorAuthentication())
<div class="mb-6">
@livewire('profile.two-factor-authentication-form')
</div>
@endif
@if (Laravel\Jetstream\Jetstream::hasAccountDeletionFeatures())
@livewire('profile.delete-user-form')
@endif
<div class="mb-6">
@livewire('profile.logout-other-browser-sessions-form')
</div>
@if (Laravel\Jetstream\Jetstream::hasAccountDeletionFeatures())
@livewire('profile.delete-user-form')
@endif
*/ ?>
@endsection

View File

@ -17,5 +17,5 @@
@endsection
@section('content')
@livewire('role-card')
@livewire('vuexy-admin::roles-index')
@endsection

View File

@ -24,12 +24,7 @@
<div class="row">
<div class="col-md-4">
<div class="mb-4">
@livewire('mail-smtp-settings')
</div>
</div>
<div class="col-md-4">
<div class="mb-4">
@livewire('mail-sender-response-settings')
@livewire('vuexy-admin::sendmail-settings')
</div>
</div>
</div>

View File

@ -24,6 +24,6 @@
@endpush
@section('content')
@livewire('user-index')
@livewire('user-offcanvas-form')
@livewire('vuexy-admin::users-index')
@livewire('vuexy-admin::user-offcanvas-form')
@endsection