Prepare modules
This commit is contained in:
@ -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();
|
||||
|
@ -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 '';
|
||||
|
@ -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',
|
||||
|
79
resources/assets/js/forms/disabledForm.js
Normal file
79
resources/assets/js/forms/disabledForm.js
Normal 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
10
resources/assets/js/forms/slugify.js
Normal file
10
resources/assets/js/forms/slugify.js
Normal 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, '-');
|
||||
};
|
27
resources/assets/js/livewire/registerLivewireHookOnce.js
Normal file
27
resources/assets/js/livewire/registerLivewireHookOnce.js
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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;
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
2
resources/assets/vendor/scss/core-dark.scss
vendored
2
resources/assets/vendor/scss/core-dark.scss
vendored
@ -2,3 +2,5 @@
|
||||
@import 'bootstrap-extended-dark';
|
||||
@import 'components-dark';
|
||||
@import 'colors-dark';
|
||||
|
||||
$primary-color: #4a8c08;
|
||||
|
2
resources/assets/vendor/scss/core.scss
vendored
2
resources/assets/vendor/scss/core.scss
vendored
@ -2,3 +2,5 @@
|
||||
@import 'bootstrap-extended';
|
||||
@import 'components';
|
||||
@import 'colors';
|
||||
|
||||
$primary-color: #4a8c08;
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
|
||||
body {
|
||||
background: $card-bg;
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
$body-bg: #f8f7fa;
|
||||
|
||||
body {
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
|
||||
body {
|
||||
background: $body-bg;
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
$body-bg: #f8f7fa;
|
||||
|
||||
body {
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
|
||||
body {
|
||||
background: $body-bg;
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
$body-bg: #f8f7fa;
|
||||
|
||||
body {
|
||||
|
Reference in New Issue
Block a user