first commit
This commit is contained in:
245
resources/assets/js/bootstrap-table/bootstrapTableManager.js
vendored
Normal file
245
resources/assets/js/bootstrap-table/bootstrapTableManager.js
vendored
Normal file
@ -0,0 +1,245 @@
|
||||
import '../../vendor/libs/bootstrap-table/bootstrap-table';
|
||||
import '../notifications/LivewireNotification';
|
||||
|
||||
class BootstrapTableManager {
|
||||
constructor(bootstrapTableWrap, config = {}) {
|
||||
const defaultConfig = {
|
||||
header: [],
|
||||
format: [],
|
||||
search_columns: [],
|
||||
actionColumn: false,
|
||||
height: 'auto',
|
||||
minHeight: 300,
|
||||
bottomMargin : 195,
|
||||
search: true,
|
||||
showColumns: true,
|
||||
showColumnsToggleAll: true,
|
||||
showExport: true,
|
||||
exportfileName: 'datatTable',
|
||||
exportWithDatetime: true,
|
||||
showFullscreen: true,
|
||||
showPaginationSwitch: true,
|
||||
showRefresh: true,
|
||||
showToggle: true,
|
||||
/*
|
||||
smartDisplay: false,
|
||||
searchOnEnterKey: true,
|
||||
showHeader: false,
|
||||
showFooter: true,
|
||||
showRefresh: true,
|
||||
showToggle: true,
|
||||
showFullscreen: true,
|
||||
detailView: true,
|
||||
searchAlign: 'right',
|
||||
buttonsAlign: 'right',
|
||||
toolbarAlign: 'left',
|
||||
paginationVAlign: 'bottom',
|
||||
paginationHAlign: 'right',
|
||||
paginationDetailHAlign: 'left',
|
||||
paginationSuccessivelySize: 5,
|
||||
paginationPagesBySide: 3,
|
||||
paginationUseIntermediate: true,
|
||||
*/
|
||||
clickToSelect: true,
|
||||
minimumCountColumns: 4,
|
||||
fixedColumns: true,
|
||||
fixedNumber: 1,
|
||||
idField: 'id',
|
||||
pagination: true,
|
||||
pageList: [25, 50, 100, 500, 1000],
|
||||
sortName: 'id',
|
||||
sortOrder: 'asc',
|
||||
cookie: false,
|
||||
cookieExpire: '365d',
|
||||
cookieIdTable: 'myTableCookies', // Nombre único para las cookies de la tabla
|
||||
cookieStorage: 'localStorage',
|
||||
cookiePath: '/',
|
||||
};
|
||||
|
||||
this.$bootstrapTable = $('.bootstrap-table', bootstrapTableWrap);
|
||||
this.$toolbar = $('.bt-toolbar', bootstrapTableWrap);
|
||||
this.$searchColumns = $('.search_columns', bootstrapTableWrap);
|
||||
this.$btnRefresh = $('.btn-refresh', bootstrapTableWrap);
|
||||
this.$btnClearFilters = $('.btn-clear-filters', bootstrapTableWrap);
|
||||
|
||||
this.config = { ...defaultConfig, ...config };
|
||||
|
||||
this.config.toolbar = `${bootstrapTableWrap} .bt-toolbar`;
|
||||
this.config.height = this.config.height == 'auto'? this.getTableHeight(): this.config.height;
|
||||
this.config.cookieIdTable = this.config.exportWithDatetime? this.config.cookieIdTable + '-' + this.getFormattedDateYMDHm(): this.config.cookieIdTable;
|
||||
|
||||
this.tableFormatters = {}; // Mueve la carga de formatters aquí
|
||||
|
||||
this.initTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula la altura de la tabla.
|
||||
*/
|
||||
getTableHeight() {
|
||||
const btHeight = window.innerHeight - this.$toolbar.height() - this.bottomMargin;
|
||||
|
||||
return btHeight < this.config.minHeight ? this.config.minHeight : btHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera un ID único para la tabla basado en una cookie.
|
||||
*/
|
||||
getCookieId() {
|
||||
const generateShortHash = (str) => {
|
||||
let hash = 0;
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash &= hash; // Convertir a entero de 32 bits
|
||||
}
|
||||
|
||||
return Math.abs(hash).toString().substring(0, 12);
|
||||
};
|
||||
|
||||
return `bootstrap-table-cache-${generateShortHash(this.config.title)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga los formatters dinámicamente
|
||||
*/
|
||||
async loadFormatters() {
|
||||
const formattersModules = import.meta.glob('../../../../../**/resources/assets/js/bootstrap-table/*Formatters.js');
|
||||
|
||||
const formatterPromises = Object.entries(formattersModules).map(async ([path, importer]) => {
|
||||
const module = await importer();
|
||||
Object.assign(this.tableFormatters, module);
|
||||
});
|
||||
|
||||
await Promise.all(formatterPromises);
|
||||
}
|
||||
|
||||
btColumns() {
|
||||
const columns = [];
|
||||
|
||||
Object.entries(this.config.header).forEach(([key, value]) => {
|
||||
const columnFormat = this.config.format[key] || {};
|
||||
|
||||
if (typeof columnFormat.formatter === 'object') {
|
||||
const formatterName = columnFormat.formatter.name;
|
||||
const formatterParams = columnFormat.formatter.params || {};
|
||||
|
||||
const formatterFunction = this.tableFormatters[formatterName];
|
||||
if (formatterFunction) {
|
||||
columnFormat.formatter = (value, row, index) => formatterFunction(value, row, index, formatterParams);
|
||||
} else {
|
||||
console.warn(`Formatter "${formatterName}" no encontrado para la columna "${key}"`);
|
||||
}
|
||||
} else if (typeof columnFormat.formatter === 'string') {
|
||||
const formatterFunction = this.tableFormatters[columnFormat.formatter];
|
||||
if (formatterFunction) {
|
||||
columnFormat.formatter = formatterFunction;
|
||||
}
|
||||
}
|
||||
|
||||
if (columnFormat.onlyFormatter) {
|
||||
columns.push({
|
||||
align: 'center',
|
||||
formatter: columnFormat.formatter || (() => ''),
|
||||
forceHide: true,
|
||||
switchable: false,
|
||||
field: key,
|
||||
title: value,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const column = {
|
||||
title: value,
|
||||
field: key,
|
||||
sortable: true,
|
||||
};
|
||||
|
||||
columns.push({ ...column, ...columnFormat });
|
||||
});
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Petición AJAX para la tabla.
|
||||
*/
|
||||
ajaxRequest(params) {
|
||||
const url = `${window.location.href}?${$.param(params.data)}&${$('.bt-toolbar :input').serialize()}`;
|
||||
|
||||
$.get(url).then((res) => params.success(res));
|
||||
}
|
||||
|
||||
toValidFilename(str, extension = 'txt') {
|
||||
return str
|
||||
.normalize("NFD") // 🔹 Normaliza caracteres con tilde
|
||||
.replace(/[\u0300-\u036f]/g, "") // 🔹 Elimina acentos y diacríticos
|
||||
.replace(/[<>:"\/\\|?*\x00-\x1F]/g, '') // 🔹 Elimina caracteres inválidos
|
||||
.replace(/\s+/g, '-') // 🔹 Reemplaza espacios con guiones
|
||||
.replace(/-+/g, '-') // 🔹 Evita múltiples guiones seguidos
|
||||
.replace(/^-+|-+$/g, '') // 🔹 Elimina guiones al inicio y fin
|
||||
.toLowerCase() // 🔹 Convierte a minúsculas
|
||||
+ (extension ? '.' + extension.replace(/^\.+/, '') : ''); // 🔹 Asegura la extensión válida
|
||||
}
|
||||
|
||||
getFormattedDateYMDHm(date = new Date()) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0'); // 🔹 Asegura dos dígitos
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
|
||||
return `${year}${month}${day}-${hours}${minutes}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inicia la tabla después de cargar los formatters
|
||||
*/
|
||||
async initTable() {
|
||||
await this.loadFormatters(); // Asegura que los formatters estén listos antes de inicializar
|
||||
|
||||
this.$bootstrapTable
|
||||
.bootstrapTable('destroy').bootstrapTable({
|
||||
height: this.config.height,
|
||||
locale: 'es-MX',
|
||||
ajax: (params) => this.ajaxRequest(params),
|
||||
toolbar: this.config.toolbar,
|
||||
search: this.config.search,
|
||||
showColumns: this.config.showColumns,
|
||||
showColumnsToggleAll: this.config.showColumnsToggleAll,
|
||||
showExport: this.config.showExport,
|
||||
exportTypes: ['csv', 'txt', 'xlsx'],
|
||||
exportOptions: {
|
||||
fileName: this.config.fileName,
|
||||
},
|
||||
showFullscreen: this.config.showFullscreen,
|
||||
showPaginationSwitch: this.config.showPaginationSwitch,
|
||||
showRefresh: this.config.showRefresh,
|
||||
showToggle: this.config.showToggle,
|
||||
clickToSelect: this.config.clickToSelect,
|
||||
minimumCountColumns: this.config.minimumCountColumns,
|
||||
fixedColumns: this.config.fixedColumns,
|
||||
fixedNumber: this.config.fixedNumber,
|
||||
idField: this.config.idField,
|
||||
pagination: this.config.pagination,
|
||||
pageList: this.config.pageList,
|
||||
sidePagination: "server",
|
||||
sortName: this.config.sortName,
|
||||
sortOrder: this.config.sortOrder,
|
||||
mobileResponsive: true,
|
||||
resizable: true,
|
||||
cookie: this.config.cookie,
|
||||
cookieExpire: this.config.cookieExpire,
|
||||
cookieIdTable: this.config.cookieIdTable,
|
||||
columns: this.btColumns(),
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
window.BootstrapTableManager = BootstrapTableManager;
|
132
resources/assets/js/bootstrap-table/globalConfig.js
Normal file
132
resources/assets/js/bootstrap-table/globalConfig.js
Normal file
@ -0,0 +1,132 @@
|
||||
const appRoutesElement = document.getElementById('app-routes');
|
||||
|
||||
export const routes = appRoutesElement ? JSON.parse(appRoutesElement.textContent) : {};
|
||||
|
||||
export const booleanStatusCatalog = {
|
||||
activo: {
|
||||
trueText: 'Activo',
|
||||
falseText: 'Inactivo',
|
||||
trueClass: 'badge bg-label-success',
|
||||
falseClass: 'badge bg-label-danger',
|
||||
},
|
||||
habilitado: {
|
||||
trueText: 'Habilitado',
|
||||
falseText: 'Deshabilitado',
|
||||
trueClass: 'badge bg-label-success',
|
||||
falseClass: 'badge bg-label-danger',
|
||||
trueIcon: 'ti ti-checkup-list',
|
||||
falseIcon: 'ti ti-ban',
|
||||
},
|
||||
checkSI: {
|
||||
trueText: 'SI',
|
||||
falseIcon: '',
|
||||
trueClass: 'badge bg-label-info',
|
||||
falseText: '',
|
||||
},
|
||||
check: {
|
||||
trueIcon: 'ti ti-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
checkbox: {
|
||||
trueIcon: 'ti ti-checkbox',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
checklist: {
|
||||
trueIcon: 'ti ti-checklist',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
phone_done: {
|
||||
trueIcon: 'ti ti-phone-done',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
checkup_list: {
|
||||
trueIcon: 'ti ti-checkup-list',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
list_check: {
|
||||
trueIcon: 'ti ti-list-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
camera_check: {
|
||||
trueIcon: 'ti ti-camera-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
mail_check: {
|
||||
trueIcon: 'ti ti-mail-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
clock_check: {
|
||||
trueIcon: 'ti ti-clock-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
user_check: {
|
||||
trueIcon: 'ti ti-user-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
circle_check: {
|
||||
trueIcon: 'ti ti-circle-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
shield_check: {
|
||||
trueIcon: 'ti ti-shield-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
calendar_check: {
|
||||
trueIcon: 'ti ti-calendar-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
}
|
||||
};
|
||||
|
||||
export const badgeColorCatalog = {
|
||||
primary: { color: 'primary' },
|
||||
secondary: { color: 'secondary' },
|
||||
success: { color: 'success' },
|
||||
danger: { color: 'danger' },
|
||||
warning: { color: 'warning' },
|
||||
info: { color: 'info' },
|
||||
dark: { color: 'dark' },
|
||||
light: { color: 'light', textColor: 'text-dark' }
|
||||
};
|
||||
|
||||
export const statusIntBadgeBgCatalogCss = {
|
||||
1: 'warning',
|
||||
2: 'info',
|
||||
10: 'success',
|
||||
12: 'danger',
|
||||
11: 'warning'
|
||||
};
|
||||
|
||||
export const statusIntBadgeBgCatalog = {
|
||||
1: 'Inactivo',
|
||||
2: 'En proceso',
|
||||
10: 'Activo',
|
||||
11: 'Archivado',
|
||||
12: 'Cancelado',
|
||||
};
|
||||
|
193
resources/assets/js/bootstrap-table/globalFormatters.js
Normal file
193
resources/assets/js/bootstrap-table/globalFormatters.js
Normal file
@ -0,0 +1,193 @@
|
||||
import { booleanStatusCatalog, statusIntBadgeBgCatalogCss, statusIntBadgeBgCatalog } from './globalConfig';
|
||||
import {routes} from '../../../../../laravel-vuexy-admin/resources/assets/js/bootstrap-table/globalConfig.js';
|
||||
|
||||
export const userActionFormatter = (value, row, index) => {
|
||||
if (!row.id) return '';
|
||||
|
||||
const showUrl = routes['admin.user.show'].replace(':id', row.id);
|
||||
const editUrl = routes['admin.user.edit'].replace(':id', row.id);
|
||||
const deleteUrl = routes['admin.user.delete'].replace(':id', row.id);
|
||||
|
||||
return `
|
||||
<div class="flex space-x-2">
|
||||
<a href="${editUrl}" title="Editar" class="icon-button hover:text-slate-700">
|
||||
<i class="ti ti-edit"></i>
|
||||
</a>
|
||||
<a href="${deleteUrl}" title="Eliminar" class="icon-button hover:text-slate-700">
|
||||
<i class="ti ti-trash"></i>
|
||||
</a>
|
||||
<a href="${showUrl}" title="Ver" class="icon-button hover:text-slate-700">
|
||||
<i class="ti ti-eye"></i>
|
||||
</a>
|
||||
</div>
|
||||
`.trim();
|
||||
};
|
||||
|
||||
export const dynamicBooleanFormatter = (value, row, index, options = {}) => {
|
||||
const { tag = 'default', customOptions = {} } = options;
|
||||
const catalogConfig = booleanStatusCatalog[tag] || {};
|
||||
|
||||
const finalOptions = {
|
||||
...catalogConfig,
|
||||
...customOptions, // Permite sobreescribir la configuración predeterminada
|
||||
...options // Permite pasar opciones rápidas
|
||||
};
|
||||
|
||||
const {
|
||||
trueIcon = '',
|
||||
falseIcon = '',
|
||||
trueText = 'Sí',
|
||||
falseText = 'No',
|
||||
trueClass = 'badge bg-label-success',
|
||||
falseClass = 'badge bg-label-danger',
|
||||
iconClass = 'text-green-800'
|
||||
} = finalOptions;
|
||||
|
||||
const trueElement = !trueIcon && !trueText ? '' : `<span class="${trueClass}">${trueIcon ? `<i class="${trueIcon} ${iconClass}"></i> ` : ''}${trueText}</span>`;
|
||||
const falseElement = !falseIcon && !falseText ? '' : `<span class="${falseClass}">${falseIcon ? `<i class="${falseIcon}"></i> ` : ''}${falseText}</span>`;
|
||||
|
||||
return value? trueElement : falseElement;
|
||||
};
|
||||
|
||||
export const dynamicBadgeFormatter = (value, row, index, options = {}) => {
|
||||
const {
|
||||
color = 'primary', // Valor por defecto
|
||||
textColor = '', // Permite agregar color de texto si es necesario
|
||||
additionalClass = '' // Permite añadir clases adicionales
|
||||
} = options;
|
||||
|
||||
return `<span class="badge bg-${color} ${textColor} ${additionalClass}">${value}</span>`;
|
||||
};
|
||||
|
||||
export const statusIntBadgeBgFormatter = (value, row, index) => {
|
||||
return value
|
||||
? `<span class="badge bg-label-${statusIntBadgeBgCatalogCss[value]}">${statusIntBadgeBgCatalog[value]}</span>`
|
||||
: '';
|
||||
}
|
||||
|
||||
export const textNowrapFormatter = (value, row, index) => {
|
||||
if (!value) return '';
|
||||
return `<span class="text-nowrap">${value}</span>`;
|
||||
}
|
||||
|
||||
|
||||
export const toCurrencyFormatter = (value, row, index) => {
|
||||
return isNaN(value) ? '' : Number(value).toCurrency();
|
||||
}
|
||||
|
||||
export const numberFormatter = (value, row, index) => {
|
||||
return isNaN(value) ? '' : Number(value);
|
||||
}
|
||||
|
||||
export const monthFormatter = (value, row, index) => {
|
||||
switch (parseInt(value)) {
|
||||
case 1:
|
||||
return 'Enero';
|
||||
case 2:
|
||||
return 'Febrero';
|
||||
case 3:
|
||||
return 'Marzo';
|
||||
case 4:
|
||||
return 'Abril';
|
||||
case 5:
|
||||
return 'Mayo';
|
||||
case 6:
|
||||
return 'Junio';
|
||||
case 7:
|
||||
return 'Julio';
|
||||
case 8:
|
||||
return 'Agosto';
|
||||
case 9:
|
||||
return 'Septiembre';
|
||||
case 10:
|
||||
return 'Octubre';
|
||||
case 11:
|
||||
return 'Noviembre';
|
||||
case 12:
|
||||
return 'Diciembre';
|
||||
}
|
||||
}
|
||||
|
||||
export const humaneTimeFormatter = (value, row, index) => {
|
||||
return isNaN(value) ? '' : Number(value).humaneTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera la URL del avatar basado en iniciales o devuelve la foto de perfil si está disponible.
|
||||
* @param {string} fullName - Nombre completo del usuario.
|
||||
* @param {string|null} profilePhoto - Ruta de la foto de perfil.
|
||||
* @returns {string} - URL del avatar.
|
||||
*/
|
||||
function getAvatarUrl(fullName, profilePhoto) {
|
||||
const baseUrl = window.baseUrl || '';
|
||||
|
||||
if (profilePhoto) {
|
||||
return `${baseUrl}storage/profile-photos/${profilePhoto}`;
|
||||
}
|
||||
|
||||
return `${baseUrl}admin/usuario/avatar/?name=${fullName}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatea la columna del perfil de usuario con avatar, nombre y correo.
|
||||
*/
|
||||
export const userProfileFormatter = (value, row, index) => {
|
||||
if (!row.id) return '';
|
||||
|
||||
const profileUrl = routes['admin.user.show'].replace(':id', row.id);
|
||||
const avatar = getAvatarUrl(row.full_name, row.profile_photo_path);
|
||||
const email = row.email ? row.email : 'Sin correo';
|
||||
|
||||
return `
|
||||
<div class="flex items-center space-x-3" style="min-width: 240px">
|
||||
<a href="${profileUrl}" class="flex-shrink-0">
|
||||
<img src="${avatar}" alt="Avatar" class="w-10 h-10 rounded-full border border-gray-300 shadow-sm hover:scale-105 transition-transform">
|
||||
</a>
|
||||
<div class="truncate">
|
||||
<a href="${profileUrl}" class="font-medium text-slate-700 hover:underline block text-wrap">${row.full_name}</a>
|
||||
<small class="text-muted block truncate">${email}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatea la columna del perfil de contacto con avatar, nombre y correo.
|
||||
*/
|
||||
export const contactProfileFormatter = (value, row, index) => {
|
||||
if (!row.id) return '';
|
||||
|
||||
const profileUrl = routes['admin.contact.show'].replace(':id', row.id);
|
||||
const avatar = getAvatarUrl(row.full_name, row.profile_photo_path);
|
||||
const email = row.email ? row.email : 'Sin correo';
|
||||
|
||||
return `
|
||||
<div class="flex items-center space-x-3" style="min-width: 240px">
|
||||
<a href="${profileUrl}" class="flex-shrink-0">
|
||||
<img src="${avatar}" alt="Avatar" class="w-10 h-10 rounded-full border border-gray-300 shadow-sm hover:scale-105 transition-transform">
|
||||
</a>
|
||||
<div class="truncate">
|
||||
<a href="${profileUrl}" class="font-medium text-slate-700 hover:underline block text-wrap">${row.full_name}</a>
|
||||
<small class="text-muted block truncate">${email}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const creatorFormatter = (value, row, index) => {
|
||||
if (!row.creator) return '';
|
||||
|
||||
const email = row.creator_email || 'Sin correo';
|
||||
const showUrl = routes['admin.user.show'].replace(':id', row.id);
|
||||
|
||||
|
||||
return `
|
||||
<div class="flex flex-col">
|
||||
<a href="${showUrl}" class="font-medium text-slate-600 hover:underline block text-wrap">${row.creator}</a>
|
||||
<small class="text-muted">${email}</small>
|
||||
</div>
|
||||
`;
|
||||
};
|
||||
|
Reference in New Issue
Block a user