Testing Alpha
This commit is contained in:
@ -1,16 +1,15 @@
|
||||
import '../../vendor/libs/bootstrap-table/bootstrap-table';
|
||||
import '../notifications/LivewireNotification';
|
||||
import '@vuexy-admin/assets/vendor/libs/bootstrap-table/bootstrap-table';
|
||||
|
||||
class BootstrapTableManager {
|
||||
constructor(bootstrapTableWrap, config = {}) {
|
||||
const defaultConfig = {
|
||||
header: [],
|
||||
format: [],
|
||||
formatters: [],
|
||||
search_columns: [],
|
||||
actionColumn: false,
|
||||
height: 'auto',
|
||||
minHeight: 300,
|
||||
bottomMargin : 195,
|
||||
bottomMargin : 35,
|
||||
search: true,
|
||||
showColumns: true,
|
||||
showColumnsToggleAll: true,
|
||||
@ -18,7 +17,7 @@ class BootstrapTableManager {
|
||||
exportfileName: 'datatTable',
|
||||
exportWithDatetime: true,
|
||||
showFullscreen: true,
|
||||
showPaginationSwitch: true,
|
||||
showPaginationSwitch: false,
|
||||
showRefresh: true,
|
||||
showToggle: true,
|
||||
/*
|
||||
@ -49,7 +48,7 @@ class BootstrapTableManager {
|
||||
pageList: [25, 50, 100, 500, 1000],
|
||||
sortName: 'id',
|
||||
sortOrder: 'asc',
|
||||
cookie: false,
|
||||
cookie: true,
|
||||
cookieExpire: '365d',
|
||||
cookieIdTable: 'myTableCookies', // Nombre único para las cookies de la tabla
|
||||
cookieStorage: 'localStorage',
|
||||
@ -77,7 +76,10 @@ class BootstrapTableManager {
|
||||
* Calcula la altura de la tabla.
|
||||
*/
|
||||
getTableHeight() {
|
||||
const btHeight = window.innerHeight - this.$toolbar.height() - this.bottomMargin;
|
||||
let container = document.querySelector('.container-p-y'),
|
||||
toolbat = document.querySelector('.bt-toolbar');
|
||||
|
||||
let btHeight = container?.offsetHeight - toolbat?.offsetHeight - this.config.bottomMargin;
|
||||
|
||||
return btHeight < this.config.minHeight ? this.config.minHeight : btHeight;
|
||||
}
|
||||
@ -106,8 +108,7 @@ 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('/vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/*Formatters.js');
|
||||
const formattersModules = import.meta.glob('/vendor/koneko/**/resources/assets/js/bootstrap-table/*Formatters.js');
|
||||
|
||||
const formatterPromises = Object.entries(formattersModules).map(async ([path, importer]) => {
|
||||
const module = await importer();
|
||||
@ -121,7 +122,7 @@ class BootstrapTableManager {
|
||||
const columns = [];
|
||||
|
||||
Object.entries(this.config.header).forEach(([key, value]) => {
|
||||
const columnFormat = this.config.format[key] || {};
|
||||
const columnFormat = this.config.formatters[key] || {};
|
||||
|
||||
if (typeof columnFormat.formatter === 'object') {
|
||||
const formatterName = columnFormat.formatter.name;
|
||||
|
@ -29,6 +29,13 @@ export const booleanStatusCatalog = {
|
||||
trueClass: 'text-green-800',
|
||||
falseClass: '',
|
||||
},
|
||||
simpleCheck: {
|
||||
trueText: '',
|
||||
trueIcon: 'ti ti-check',
|
||||
falseIcon: '',
|
||||
trueClass: 'text-green-900',
|
||||
falseText: '',
|
||||
},
|
||||
checkbox: {
|
||||
trueIcon: 'ti ti-checkbox',
|
||||
falseIcon: '',
|
||||
@ -130,3 +137,20 @@ export const statusIntBadgeBgCatalog = {
|
||||
12: 'Cancelado',
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export const getAvatarUrl = (fullName, profilePhoto) => {
|
||||
const baseUrl = window.baseUrl || '';
|
||||
|
||||
if (profilePhoto) {
|
||||
return `${baseUrl}storage/profile-photos/${profilePhoto}`;
|
||||
}
|
||||
|
||||
const name = fullName.replace(/\s+/g, '-').toLowerCase();
|
||||
|
||||
return `${baseUrl}admin/usuario/avatar/?name=${fullName}`;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { booleanStatusCatalog, statusIntBadgeBgCatalogCss, statusIntBadgeBgCatalog } from './globalConfig';
|
||||
import {routes} from '@vuexy-admin/bootstrap-table/globalConfig.js';
|
||||
import { routes, getAvatarUrl, booleanStatusCatalog, statusIntBadgeBgCatalogCss, statusIntBadgeBgCatalog } from '@vuexy-admin/assets/js/bootstrap-table/globalConfig';
|
||||
|
||||
export const userActionFormatter = (value, row, index) => {
|
||||
if (!row.id) return '';
|
||||
@ -9,7 +8,7 @@ export const userActionFormatter = (value, row, index) => {
|
||||
const deleteUrl = routes['admin.user.delete'].replace(':id', row.id);
|
||||
|
||||
return `
|
||||
<div class="flex space-x-2">
|
||||
<div class="flex justify-center space-x-2">
|
||||
<a href="${editUrl}" title="Editar" class="icon-button hover:text-slate-700">
|
||||
<i class="ti ti-edit"></i>
|
||||
</a>
|
||||
@ -23,6 +22,25 @@ export const userActionFormatter = (value, row, index) => {
|
||||
`.trim();
|
||||
};
|
||||
|
||||
|
||||
export const userLoginActionFormatter = (value, row, index) => {
|
||||
if (!row.user_id) return '';
|
||||
|
||||
//const showUrl = routes['admin.user-login.show'].replace(':id', row.user_id);
|
||||
const showUrl = '#';
|
||||
|
||||
return `
|
||||
<div class="flex justify-center space-x-2">
|
||||
<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] || {};
|
||||
@ -50,6 +68,8 @@ export const dynamicBooleanFormatter = (value, row, index, options = {}) => {
|
||||
};
|
||||
|
||||
export const dynamicBadgeFormatter = (value, row, index, options = {}) => {
|
||||
if (!value) return '';
|
||||
|
||||
const {
|
||||
color = 'primary', // Valor por defecto
|
||||
textColor = '', // Permite agregar color de texto si es necesario
|
||||
@ -59,6 +79,11 @@ export const dynamicBadgeFormatter = (value, row, index, 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>`
|
||||
@ -71,95 +96,240 @@ export const textNowrapFormatter = (value, row, index) => {
|
||||
}
|
||||
|
||||
|
||||
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 textXsFormatter = (value, row, index) => {
|
||||
if (!value) return '';
|
||||
return `<span class="text-xs">${value}</span>`;
|
||||
};
|
||||
|
||||
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}`;
|
||||
|
||||
export const dateClassicFormatter = (value) => {
|
||||
if (!value) return '';
|
||||
|
||||
const date = new Date(value);
|
||||
|
||||
if (isNaN(date)) return value;
|
||||
|
||||
const fecha = date.toLocaleDateString('es-MX', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
const hora = value.length == 19? date.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' }): false;
|
||||
|
||||
let fechaHora = hora
|
||||
? `<span class="inline-flex items-center gap-1 text-blue-700 text-xs px-2 py-0.5 me-1">
|
||||
<i class="ti ti-calendar-event text-[14px]"></i> ${fecha}
|
||||
</span>`
|
||||
: `<span class="inline-flex items-center gap-1 text-blue-700 px-2 py-1">
|
||||
<i class="ti ti-calendar-event text-[16px]"></i> ${fecha}
|
||||
</span>`;
|
||||
|
||||
if (hora) {
|
||||
fechaHora += `
|
||||
<span class="inline-flex items-center gap-1 text-gray-700 text-xs px-2 py-0.5">
|
||||
<i class="ti ti-clock text-[14px]"></i> ${hora}
|
||||
</span>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
return `${baseUrl}admin/usuario/avatar/?name=${fullName}`;
|
||||
}
|
||||
return fechaHora.trim();
|
||||
};
|
||||
|
||||
export const dateLimeFormatter = (value) => {
|
||||
if (!value) return '';
|
||||
|
||||
const date = new Date(value);
|
||||
|
||||
if (isNaN(date)) return value;
|
||||
|
||||
const fecha = date.toLocaleDateString('es-MX', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
const hora = value.length == 19? date.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' }): false;
|
||||
|
||||
let fechaHora = hora
|
||||
? `<span class="inline-flex items-center gap-1 text-lime-800 text-xs px-2 py-0.5 me-1">
|
||||
<i class="ti ti-calendar-event text-[14px]"></i> ${fecha}
|
||||
</span>`
|
||||
: `<span class="inline-flex items-center gap-1 text-lime-800 px-2 py-1">
|
||||
<i class="ti ti-calendar-event text-[16px]"></i> ${fecha}
|
||||
</span>`;
|
||||
|
||||
if (hora) {
|
||||
fechaHora += `
|
||||
<span class="inline-flex items-center gap-1 text-lime-600 text-xs px-2 py-0.5">
|
||||
<i class="ti ti-clock text-[14px]"></i> ${hora}
|
||||
</span>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
return fechaHora.trim();
|
||||
};
|
||||
|
||||
export const dateBadgeBlueFormatter = (value) => {
|
||||
if (!value) return '';
|
||||
|
||||
const date = new Date(value);
|
||||
|
||||
if (isNaN(date)) return value;
|
||||
|
||||
const fecha = date.toLocaleDateString('es-MX', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
const hora = value.length == 19? date.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' }): false;
|
||||
|
||||
let fechaHora = hora
|
||||
? `<span class="inline-flex items-center gap-1 rounded-full bg-blue-50 text-blue-700 text-xs px-2 py-0.5 me-1">
|
||||
<i class="ti ti-calendar-event text-[14px]"></i> ${fecha}
|
||||
</span>`
|
||||
: `<span class="inline-flex items-center gap-1 rounded-full bg-blue-50 text-blue-700 px-2 py-1">
|
||||
<i class="ti ti-calendar-event text-[16px]"></i> ${fecha}
|
||||
</span>`;
|
||||
|
||||
if (hora) {
|
||||
fechaHora += `
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-gray-100 text-gray-700 text-xs px-2 py-0.5">
|
||||
<i class="ti ti-clock text-[14px]"></i> ${hora}
|
||||
</span>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
return fechaHora.trim();
|
||||
};
|
||||
|
||||
export const dateBadgeAmberFormatter = (value) => {
|
||||
if (!value) return '';
|
||||
|
||||
const date = new Date(value);
|
||||
|
||||
if (isNaN(date)) return value;
|
||||
|
||||
const fecha = date.toLocaleDateString('es-MX', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
const hora = value.length == 19? date.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' }): false;
|
||||
|
||||
let fechaHora = hora
|
||||
? `<span class="inline-flex items-center gap-1 rounded-full bg-amber-50 text-amber-700 text-xs px-2 py-0.5 me-1">
|
||||
<i class="ti ti-calendar-event text-[14px]"></i> ${fecha}
|
||||
</span>`
|
||||
: `<span class="inline-flex items-center gap-1 rounded-full bg-amber-50 text-amber-700 px-2 py-1">
|
||||
<i class="ti ti-calendar-event text-[16px]"></i> ${fecha}
|
||||
</span>`;
|
||||
|
||||
if (hora) {
|
||||
fechaHora += `
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-50 text-amber-600 text-xs px-2 py-0.5">
|
||||
<i class="ti ti-clock text-[14px]"></i> ${hora}
|
||||
</span>
|
||||
`.trim();
|
||||
}
|
||||
|
||||
return fechaHora.trim();
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export const emailFormatter = (value, row, index) => {
|
||||
if (!value) return '';
|
||||
|
||||
return `
|
||||
<a href="mailto:${value}" class="flex items-center space-x-2 text-blue-600">
|
||||
<i class="ti ti-mail-filled"></i>
|
||||
<span class="whitespace-nowrap hover:underline">${value}</span>
|
||||
</a>
|
||||
`.trim();
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formatea la columna del perfil de usuario con avatar, nombre y correo.
|
||||
* Formatter para mostrar duración de sesión del usuario.
|
||||
*
|
||||
* @param {string} row.created_at - Fecha de inicio de sesión.
|
||||
* @param {string|null} row.logout_at - Fecha de cierre de sesión o null si aún activa.
|
||||
* @returns {string} Duración de la sesión formateada o estado de sesión activa.
|
||||
*/
|
||||
export const userProfileFormatter = (value, row, index) => {
|
||||
export const sessionDurationFormatter = (value, row, index) => {
|
||||
if (!row.created_at) return '<span class="text-muted">-</span>';
|
||||
|
||||
const start = new Date(row.created_at);
|
||||
const end = row.logout_at ? new Date(row.logout_at) : new Date();
|
||||
const diffMs = end - start;
|
||||
|
||||
if (diffMs < 0) return '<span class="text-danger">Invalido</span>';
|
||||
|
||||
const diffSeconds = Math.floor(diffMs / 1000);
|
||||
const hours = Math.floor(diffSeconds / 3600);
|
||||
const minutes = Math.floor((diffSeconds % 3600) / 60);
|
||||
const seconds = diffSeconds % 60;
|
||||
|
||||
const formattedTime = [
|
||||
hours ? `${hours}h` : '',
|
||||
minutes ? `${minutes}m` : '',
|
||||
`${seconds}s`
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return row.logout_at
|
||||
? `<span class="text-xs font-medium text-slate-700">${formattedTime}</span>`
|
||||
: `<span class="text-xs font-semibold text-success-600">🟢 Sesión activa (${formattedTime})</span>`;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param {int} row.id - Identificador del usuario
|
||||
* @param {string} row.full_name - Nombre completo del usuario
|
||||
* @param {string} row.profile_photo_path - Ruta de la foto de perfil
|
||||
* @param {string} row.email - Correo electrónico del usuario
|
||||
* @returns {string} HTML con el perfil del usuario
|
||||
*/
|
||||
export const userIdProfileFormatter = (value, row, index) => {
|
||||
if (!row.id) return '';
|
||||
|
||||
const avatar = getAvatarUrl(row.full_name, row.profile_photo_path);
|
||||
const email = row.email ? row.email : 'Sin correo';
|
||||
|
||||
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 formatterProfileElement(row.id, row.full_name, avatar, email, profileUrl);
|
||||
};
|
||||
|
||||
const formatterProfileElement = (id, name, avatar, email, profileUrl) => {
|
||||
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>
|
||||
<a href="${profileUrl}" class="font-medium text-slate-700 hover:underline block text-wrap">${name}</a>
|
||||
<small class="text-muted block truncate">${email}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`.trim();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Formatea la columna del perfil de contacto con avatar, nombre y correo.
|
||||
* @param {int} row.user_id - Identificador del usuario
|
||||
* @param {string} row.user_name - Nombre del usuario
|
||||
* @param {string} row.user_profile_photo_path - Ruta de la foto de perfil
|
||||
* @param {string} row.user_email - Correo electrónico del usuario
|
||||
* @returns {string} HTML con el perfil del usuario
|
||||
*/
|
||||
export const contactProfileFormatter = (value, row, index) => {
|
||||
if (!row.id) return '';
|
||||
export const userProfileFormatter = (value, row, index) => {
|
||||
if (!row.user_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';
|
||||
const profileUrl = routes['admin.user.show'].replace(':id', row.user_id);
|
||||
const avatar = getAvatarUrl(row.user_name, row.user_profile_photo_path);
|
||||
const email = row.user_email ? row.user_email : 'Sin correo';
|
||||
|
||||
return `
|
||||
<div class="flex items-center space-x-3" style="min-width: 240px">
|
||||
@ -167,27 +337,101 @@ export const contactProfileFormatter = (value, row, index) => {
|
||||
<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>
|
||||
<a href="${profileUrl}" class="font-medium text-slate-700 hover:underline block text-wrap">${row.user_name}</a>
|
||||
<small class="text-muted block truncate">${email}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`.trim();
|
||||
};
|
||||
|
||||
|
||||
|
||||
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);
|
||||
export const creatorProfileFormatter = (value, row, index) => {
|
||||
if (!row.created_by) return '';
|
||||
|
||||
const profileUrl = routes['admin.user.show'].replace(':id', row.created_by);
|
||||
const avatar = getAvatarUrl(row.creator_name, row.creator_profile_photo_path);
|
||||
const email = row.creator_email ? row.creator_email : 'Sin correo';
|
||||
|
||||
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 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.creator_name}</a>
|
||||
<small class="text-muted block truncate">${email}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`.trim();
|
||||
};
|
||||
|
||||
|
||||
export const updaterProfileFormatter = (value, row, index) => {
|
||||
if (!row.updated_by) return '';
|
||||
|
||||
const profileUrl = routes['admin.user.show'].replace(':id', row.updated_by);
|
||||
const avatar = getAvatarUrl(row.updater_name, row.updater_profile_photo_path);
|
||||
const email = row.updater_email ? row.updater_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.updater_name}</a>
|
||||
<small class="text-muted block truncate">${email}</small>
|
||||
</div>
|
||||
</div>
|
||||
`.trim();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convierte bytes en un formato legible por humanos.
|
||||
*
|
||||
* @param {int} value - Valor en bytes
|
||||
* @returns {string} Tamaño en formato legible
|
||||
*/
|
||||
export const bytesToHumanReadable = (value, row, index) => {
|
||||
if (!value || value === 0) return;
|
||||
|
||||
const precision = 2; // define aquí la precisión que desees
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const base = Math.floor(Math.log(value) / Math.log(1024));
|
||||
|
||||
return (value / Math.pow(1024, base)).toFixed(precision) + ' ' + units[base];
|
||||
};
|
||||
|
||||
|
||||
export const userRoleFormatter = (value, row, index) => {
|
||||
if (!value) return '';
|
||||
|
||||
const rolesStyles = window.userRoleStyles || {};
|
||||
|
||||
return value.split('|').map(role => {
|
||||
const trimmedRole = role.trim();
|
||||
const color = rolesStyles[trimmedRole] || 'secondary'; // fallback si no existe
|
||||
|
||||
return `<span class="badge bg-label-${color} mr-2 mb-2">${trimmedRole}</span>`;
|
||||
}).join('');
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const booleanStatusFormatter = (value, row, index) => {
|
||||
return `<span class="badge bg-label-${value ? 'success' : 'danger'}">${value ? 'Activo' : 'Inactivo'}</span>`;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user