first commit

This commit is contained in:
2025-03-07 00:29:07 -06:00
commit b21a11c2ee
564 changed files with 94041 additions and 0 deletions

View 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;

View 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',
};

View 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>
`;
};