/**
* pos-utils.js — Shared utility functions for all POS pages.
*
* Provides common operations that multiple pages need:
* - CSV export of any visible table
* - Print page (PDF via browser print dialog)
* - Toast notifications (if page doesn't have its own)
*
* Load this script in every POS template BEFORE page-specific JS.
*/
(function() {
'use strict';
// ── CSV Export ──────────────────────────────────────────────────
// Finds the first visible
on the page and downloads it as CSV.
// Works on inventory, customers, invoicing, reports, accounting.
window.exportVisibleTableCSV = function(prefix) {
prefix = prefix || 'datos';
var tables = document.querySelectorAll('table');
var table = null;
// Find first visible table with data rows
for (var i = 0; i < tables.length; i++) {
if (tables[i].offsetParent !== null && tables[i].querySelector('tbody tr')) {
table = tables[i];
break;
}
}
if (!table) {
showToast('No hay tabla de datos para exportar en esta vista.', 'warn');
return;
}
var rows = [];
// Header row
var ths = table.querySelectorAll('thead th');
if (ths.length) {
rows.push(Array.from(ths).map(function(th) {
return '"' + th.textContent.trim().replace(/"/g, '""') + '"';
}).join(','));
}
// Data rows
table.querySelectorAll('tbody tr').forEach(function(tr) {
var cells = tr.querySelectorAll('td');
rows.push(Array.from(cells).map(function(td) {
return '"' + td.textContent.trim().replace(/"/g, '""') + '"';
}).join(','));
});
if (rows.length <= 1) {
showToast('La tabla está vacía — no hay datos para exportar.', 'warn');
return;
}
var csv = rows.join('\n');
// BOM prefix so Excel opens UTF-8 correctly
var blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = prefix + '_nexus_' + new Date().toISOString().slice(0, 10) + '.csv';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast('CSV descargado: ' + a.download, 'ok');
};
// ── Print (PDF) ────────────────────────────────────────────────
window.printPage = function() {
window.print();
};
// ── Toast (enhanced with icons, progress bar, close button, actions) ──
var _toastIcons = {
ok: '',
error: '',
warn: '',
info: ''
};
var _toastTitles = { ok: 'Éxito', error: 'Error', warn: 'Advertencia', info: 'Información' };
window.showToast = function(msg, type, opts) {
type = type || 'info';
opts = opts || {};
var container = document.getElementById('toast-container');
if (!container) {
container = document.createElement('div');
container.id = 'toast-container';
document.body.appendChild(container);
}
var toast = document.createElement('div');
toast.className = 'toast toast--' + type;
var iconHtml = '
' + (_toastIcons[type] || _toastIcons.info) + '
';
var titleHtml = opts.title ? '
' + opts.title + '
' : '';
var actionHtml = '';
if (opts.action && opts.action.text) {
actionHtml = '';
toast.__toastAction = function() {
if (opts.action.callback) opts.action.callback();
_removeToast(toast);
};
}
var progressHtml = '';
toast.innerHTML = iconHtml +
'
' + titleHtml + '
' + msg + '
' + actionHtml + '
' +
'' +
progressHtml;
container.appendChild(toast);
var timer = setTimeout(function() { _removeToast(toast); }, opts.duration || 4000);
toast.__toastTimer = timer;
toast.addEventListener('mouseenter', function() { clearTimeout(timer); var p = toast.querySelector('.toast__progress'); if (p) p.style.animationPlayState = 'paused'; });
toast.addEventListener('mouseleave', function() { var p = toast.querySelector('.toast__progress'); if (p) p.style.animationPlayState = 'running'; timer = setTimeout(function() { _removeToast(toast); }, 2000); toast.__toastTimer = timer; });
};
window._removeToast = function(toast) {
if (!toast || toast.__toastRemoved) return;
toast.__toastRemoved = true;
if (toast.__toastTimer) clearTimeout(toast.__toastTimer);
toast.style.animation = 'toastSlideOut 0.25s ease forwards';
setTimeout(function() { toast.remove(); }, 260);
};
// ── Skeleton helpers ──────────────────────────────────────────
window.renderSkeletonRows = function(cols, rows) {
rows = rows || 6;
var html = '';
for (var i = 0; i < rows; i++) {
html += '
';
for (var j = 0; j < cols; j++) {
html += '
';
}
html += '
';
}
return html;
};
window.showSkeleton = function(containerSelector, cols, rows) {
var el = typeof containerSelector === 'string' ? document.querySelector(containerSelector) : containerSelector;
if (!el) return;
el.dataset.originalContent = el.innerHTML;
el.innerHTML = renderSkeletonRows(cols || 6, rows || 6);
};
window.hideSkeleton = function(containerSelector) {
var el = typeof containerSelector === 'string' ? document.querySelector(containerSelector) : containerSelector;
if (!el || el.dataset.originalContent === undefined) return;
el.innerHTML = el.dataset.originalContent;
delete el.dataset.originalContent;
};
// ── Empty state helper ────────────────────────────────────────
window.renderEmptyState = function(opts) {
opts = opts || {};
var icon = opts.icon || '';
var title = opts.title || 'Sin datos';
var subtitle = opts.subtitle || 'No hay información disponible en este momento.';
var action = opts.action ? '
' + opts.action + '
' : '';
return '
' +
'
' + icon + '
' +
'
' + title + '
' +
'
' + subtitle + '
' +
action + '
';
};
// ── Cmd+K Global Search ───────────────────────────────────────
(function() {
var cmdkOverlay = null, cmdkInput = null, cmdkResults = null, cmdkSelected = -1;
var cmdkItems = [];
function buildCmdK() {
if (cmdkOverlay) return;
cmdkOverlay = document.createElement('div');
cmdkOverlay.className = 'cmdk-overlay';
cmdkOverlay.innerHTML =
'
' +
'
' +
' ' +
' ' +
' ESC' +
'
' +
' ' +
' ' +
'
';
document.body.appendChild(cmdkOverlay);
cmdkInput = cmdkOverlay.querySelector('.cmdk-input');
cmdkResults = cmdkOverlay.querySelector('.cmdk-results');
cmdkOverlay.addEventListener('click', function(e) { if (e.target === cmdkOverlay) closeCmdK(); });
cmdkInput.addEventListener('input', function() { filterCmdK(this.value); });
cmdkInput.addEventListener('keydown', function(e) {
if (e.key === 'Escape') { closeCmdK(); return; }
if (e.key === 'ArrowDown') { e.preventDefault(); moveCmdK(1); }
if (e.key === 'ArrowUp') { e.preventDefault(); moveCmdK(-1); }
if (e.key === 'Enter') { e.preventDefault(); activateCmdK(); }
});
}
function openCmdK() {
buildCmdK();
cmdkOverlay.classList.add('is-open');
cmdkInput.value = '';
cmdkInput.focus();
filterCmdK('');
}
function closeCmdK() {
if (cmdkOverlay) cmdkOverlay.classList.remove('is-open');
}
function moveCmdK(dir) {
var items = cmdkResults.querySelectorAll('.cmdk-item');
if (!items.length) return;
cmdkSelected += dir;
if (cmdkSelected < 0) cmdkSelected = items.length - 1;
if (cmdkSelected >= items.length) cmdkSelected = 0;
items.forEach(function(it, i) { it.classList.toggle('is-selected', i === cmdkSelected); });
var sel = items[cmdkSelected];
if (sel) sel.scrollIntoView({ block: 'nearest' });
}
function activateCmdK() {
var items = cmdkResults.querySelectorAll('.cmdk-item');
var sel = items[cmdkSelected];
if (sel && sel.dataset.href) { closeCmdK(); window.location.href = sel.dataset.href; }
}
function filterCmdK(q) {
q = (q || '').toLowerCase().trim();
var groups = {};
cmdkItems.forEach(function(item) {
if (!q || item.label.toLowerCase().indexOf(q) !== -1 || (item.keywords || '').toLowerCase().indexOf(q) !== -1) {
groups[item.group] = groups[item.group] || [];
groups[item.group].push(item);
}
});
var html = '';
var total = 0;
Object.keys(groups).forEach(function(g) {
html += '
' + g + '
';
groups[g].forEach(function(item) {
total++;
html += '