188 lines
7.5 KiB
JavaScript
188 lines
7.5 KiB
JavaScript
/**
|
|
* tienda.js — Store / Tablet dashboard logic for Nexus Autoparts
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
var API = '';
|
|
|
|
// ================================================================
|
|
// Utility
|
|
// ================================================================
|
|
|
|
function fmt(n) {
|
|
return '$' + (parseFloat(n) || 0).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
}
|
|
|
|
function esc(s) {
|
|
if (!s) return '';
|
|
var d = document.createElement('div');
|
|
d.textContent = s;
|
|
return d.innerHTML;
|
|
}
|
|
|
|
// ================================================================
|
|
// Clock
|
|
// ================================================================
|
|
|
|
function updateClock() {
|
|
var now = new Date();
|
|
var h = now.getHours();
|
|
var m = String(now.getMinutes()).padStart(2, '0');
|
|
var ampm = h >= 12 ? 'PM' : 'AM';
|
|
h = h % 12 || 12;
|
|
document.getElementById('clock').textContent = h + ':' + m + ' ' + ampm;
|
|
}
|
|
|
|
updateClock();
|
|
setInterval(updateClock, 30000);
|
|
|
|
// ================================================================
|
|
// Load Dashboard Stats
|
|
// ================================================================
|
|
|
|
function loadStats() {
|
|
fetch(API + '/api/tienda/stats')
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (d) {
|
|
var st = d.sales_today || {};
|
|
var sm = d.sales_month || {};
|
|
var pt = d.payments_today || {};
|
|
|
|
// KPIs
|
|
document.getElementById('kpi-sales-today').textContent = fmt(st.total);
|
|
document.getElementById('kpi-sales-count').textContent = (st.count || 0) + ' facturas';
|
|
document.getElementById('kpi-month').textContent = fmt(sm.total);
|
|
document.getElementById('kpi-month-count').textContent = (sm.count || 0) + ' facturas';
|
|
document.getElementById('kpi-customers').textContent = d.total_customers || 0;
|
|
document.getElementById('kpi-parts-count').textContent = (d.total_parts || 0) + ' partes';
|
|
document.getElementById('kpi-pending').textContent = fmt(d.pending_balance || 0);
|
|
document.getElementById('kpi-pending-count').textContent = (d.pending_invoices || 0) + ' facturas';
|
|
|
|
// Today's payments
|
|
document.getElementById('payments-today-amount').textContent = fmt(pt.total);
|
|
document.getElementById('payments-today-count').textContent = (pt.count || 0) + ' pagos registrados';
|
|
|
|
// Top debtors
|
|
renderDebtors(d.top_debtors || []);
|
|
|
|
// Recent invoices
|
|
renderInvoices(d.recent_invoices || []);
|
|
})
|
|
.catch(function (err) {
|
|
console.error('Error loading stats:', err);
|
|
});
|
|
}
|
|
|
|
// ================================================================
|
|
// Render Debtors
|
|
// ================================================================
|
|
|
|
function renderDebtors(debtors) {
|
|
var el = document.getElementById('debtors-list');
|
|
|
|
if (debtors.length === 0) {
|
|
el.innerHTML = '<div class="t-empty">Sin cuentas pendientes</div>';
|
|
return;
|
|
}
|
|
|
|
el.innerHTML = debtors.map(function (d) {
|
|
var limitPct = d.credit_limit > 0 ? Math.round(d.balance / d.credit_limit * 100) : 0;
|
|
return '<a href="/cuentas" class="t-debtor">'
|
|
+ '<div>'
|
|
+ '<div class="t-debtor-name">' + esc(d.name) + '</div>'
|
|
+ (d.credit_limit > 0 ? '<div class="t-debtor-invoices">' + limitPct + '% de l\u00edmite</div>' : '')
|
|
+ '</div>'
|
|
+ '<span class="t-debtor-amount">' + fmt(d.balance) + '</span>'
|
|
+ '</a>';
|
|
}).join('');
|
|
}
|
|
|
|
// ================================================================
|
|
// Render Recent Invoices
|
|
// ================================================================
|
|
|
|
function renderInvoices(invoices) {
|
|
var el = document.getElementById('recent-invoices');
|
|
|
|
if (invoices.length === 0) {
|
|
el.innerHTML = '<div class="t-empty">Sin facturas recientes</div>';
|
|
return;
|
|
}
|
|
|
|
el.innerHTML = invoices.map(function (inv) {
|
|
var statusClass = inv.status || 'pending';
|
|
var statusLabel = { pending: 'Pendiente', paid: 'Pagada', partial: 'Parcial', cancelled: 'Cancelada' };
|
|
return '<div class="t-invoice">'
|
|
+ '<div class="t-invoice-left">'
|
|
+ '<span class="t-invoice-folio">' + esc(inv.folio) + '</span>'
|
|
+ '<span class="t-invoice-customer">' + esc(inv.customer_name) + '</span>'
|
|
+ '</div>'
|
|
+ '<div class="t-invoice-right">'
|
|
+ '<span class="t-invoice-total">' + fmt(inv.total) + '</span>'
|
|
+ '<span class="t-invoice-status ' + statusClass + '">' + (statusLabel[statusClass] || statusClass) + '</span>'
|
|
+ '</div>'
|
|
+ '</div>';
|
|
}).join('');
|
|
}
|
|
|
|
// ================================================================
|
|
// Global Search
|
|
// ================================================================
|
|
|
|
var searchTimer = null;
|
|
var searchInput = document.getElementById('global-search');
|
|
var searchResults = document.getElementById('global-results');
|
|
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', function () {
|
|
clearTimeout(searchTimer);
|
|
var q = this.value.trim();
|
|
if (q.length < 2) {
|
|
searchResults.classList.remove('active');
|
|
searchResults.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
searchTimer = setTimeout(function () {
|
|
fetch(API + '/api/pos/search-parts?q=' + encodeURIComponent(q))
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (results) {
|
|
if (results.length === 0) {
|
|
searchResults.innerHTML = '<div style="padding:0.8rem;color:var(--text-secondary);font-size:0.85rem">Sin resultados para "' + esc(q) + '"</div>';
|
|
} else {
|
|
searchResults.innerHTML = results.slice(0, 8).map(function (p) {
|
|
return '<div class="t-search-result-item">'
|
|
+ '<div>'
|
|
+ '<span class="sri-number">' + esc(p.oem_part_number) + '</span>'
|
|
+ '<span class="sri-name">' + esc(p.name_part) + '</span>'
|
|
+ '</div>'
|
|
+ '</div>';
|
|
}).join('');
|
|
}
|
|
searchResults.classList.add('active');
|
|
});
|
|
}, 250);
|
|
});
|
|
|
|
searchInput.addEventListener('blur', function () {
|
|
setTimeout(function () { searchResults.classList.remove('active'); }, 200);
|
|
});
|
|
|
|
searchInput.addEventListener('focus', function () {
|
|
if (searchResults.innerHTML.trim()) {
|
|
searchResults.classList.add('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
// ================================================================
|
|
// Init
|
|
// ================================================================
|
|
|
|
loadStats();
|
|
|
|
// Auto-refresh every 2 minutes
|
|
setInterval(loadStats, 120000);
|
|
})();
|