344 lines
14 KiB
JavaScript
344 lines
14 KiB
JavaScript
/**
|
|
* app-init.js — Shared initialization for all POS pages
|
|
*
|
|
* Handles:
|
|
* 1. Auth check (redirect to login if no valid token)
|
|
* 2. Set real employee name/role in sidebar and header
|
|
* 3. Set navigation links as active based on current page
|
|
* 4. Sidebar toggle for mobile
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
// ─── Auth Check ───
|
|
var token = localStorage.getItem('pos_token');
|
|
if (!token) {
|
|
window.location.href = '/pos/login';
|
|
return;
|
|
}
|
|
|
|
// Validate token not expired
|
|
try {
|
|
var payload = JSON.parse(atob(token.split('.')[1]));
|
|
if (payload.exp * 1000 < Date.now()) {
|
|
localStorage.removeItem('pos_token');
|
|
localStorage.removeItem('pos_employee');
|
|
window.location.href = '/pos/login';
|
|
return;
|
|
}
|
|
} catch(e) {
|
|
localStorage.removeItem('pos_token');
|
|
window.location.href = '/pos/login';
|
|
return;
|
|
}
|
|
|
|
// ─── Get employee info ───
|
|
var employee = {};
|
|
try {
|
|
employee = JSON.parse(localStorage.getItem('pos_employee') || '{}');
|
|
} catch(e) {}
|
|
|
|
var name = employee.name || payload.name || 'Usuario';
|
|
var role = employee.role || payload.role || '';
|
|
var _t = typeof window.t === 'function' ? window.t : function(k) { return k; };
|
|
var roleLabels = {
|
|
'owner': _t('role_owner'), 'admin': _t('role_admin'), 'cashier': _t('role_cashier'),
|
|
'warehouse': _t('role_warehouse'), 'accountant': _t('role_accountant'),
|
|
'workshop': 'Taller', 'mechanic': 'Mecanico'
|
|
};
|
|
var roleLabel = roleLabels[role] || role;
|
|
var initials = name.split(' ').map(function(p) { return p[0]; }).join('').toUpperCase().substring(0, 2);
|
|
|
|
// ─── Replace hardcoded names in sidebar ───
|
|
// Sidebar user name
|
|
document.querySelectorAll('.sidebar__user-name').forEach(function(el) {
|
|
el.textContent = name;
|
|
});
|
|
// Sidebar user role
|
|
document.querySelectorAll('.sidebar__user-role').forEach(function(el) {
|
|
el.textContent = roleLabel;
|
|
});
|
|
// Sidebar user initials/avatar
|
|
document.querySelectorAll('.sidebar__user-avatar, .sidebar__avatar').forEach(function(el) {
|
|
el.textContent = initials;
|
|
});
|
|
|
|
// Profile info in headers (catalog uses this pattern)
|
|
document.querySelectorAll('.profile-info__name').forEach(function(el) {
|
|
el.textContent = name;
|
|
});
|
|
document.querySelectorAll('.profile-info__role').forEach(function(el) {
|
|
el.textContent = roleLabel;
|
|
});
|
|
|
|
// Theme bar user labels
|
|
document.querySelectorAll('.theme-bar__label').forEach(function(el) {
|
|
if (el.textContent.indexOf('Usuario:') !== -1 || el.textContent.indexOf('Sucursal') !== -1) {
|
|
el.textContent = 'Sucursal Principal — ' + name;
|
|
}
|
|
});
|
|
|
|
// Status bar user names
|
|
document.querySelectorAll('.status-bar .user-name, .status-info span').forEach(function(el) {
|
|
var text = el.textContent;
|
|
// Replace common demo names
|
|
['Hugo M.', 'Hugo García', 'J. Ramírez', 'José Ramírez', 'Carlos M.', 'Admin'].forEach(function(demo) {
|
|
if (text.indexOf(demo) !== -1) {
|
|
el.textContent = text.replace(demo, name);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ─── Set active nav link ───
|
|
var path = window.location.pathname;
|
|
var navMap = {
|
|
'/pos/dashboard': 'dashboard',
|
|
'/pos/sale': 'pos',
|
|
'/pos/catalog': 'catalogo',
|
|
'/pos/inventory': 'inventario',
|
|
'/pos/customers': 'clientes',
|
|
'/pos/invoicing': 'facturacion',
|
|
'/pos/accounting': 'contabilidad',
|
|
'/pos/reports': 'reportes',
|
|
'/pos/config': 'configuracion'
|
|
};
|
|
|
|
document.querySelectorAll('.nav-item, .nav-link').forEach(function(link) {
|
|
link.classList.remove('is-active', 'active');
|
|
var href = link.getAttribute('href') || '';
|
|
if (href === path) {
|
|
link.classList.add('is-active');
|
|
link.classList.add('active');
|
|
}
|
|
});
|
|
|
|
// ─── Logout ───
|
|
window.posLogout = function() {
|
|
localStorage.removeItem('pos_token');
|
|
localStorage.removeItem('pos_employee');
|
|
localStorage.removeItem('pos_tenant_id');
|
|
localStorage.removeItem('pos_cart');
|
|
document.cookie = 'pos_role=; path=/pos; SameSite=Lax; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
|
window.location.href = '/pos/login';
|
|
};
|
|
|
|
// Wire any logout buttons
|
|
document.querySelectorAll('[data-action="logout"], .btn-logout, .logout-btn').forEach(function(btn) {
|
|
btn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
posLogout();
|
|
});
|
|
});
|
|
|
|
// ─── Theme management ───
|
|
// Determine theme: saved preference > system preference > default 'industrial'
|
|
var savedTheme = localStorage.getItem('pos_theme');
|
|
if (!savedTheme) {
|
|
// No saved preference — use system color scheme
|
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
savedTheme = 'industrial';
|
|
} else {
|
|
savedTheme = 'modern';
|
|
}
|
|
}
|
|
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
|
|
// Hide all theme bars (they overlap content with position:fixed)
|
|
document.querySelectorAll('.theme-bar').forEach(function(bar) {
|
|
bar.style.display = 'none';
|
|
});
|
|
|
|
// Expose theme toggle function (global)
|
|
window.posSetTheme = function(theme) {
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
localStorage.setItem('pos_theme', theme);
|
|
};
|
|
|
|
// Override any page-level setTheme functions so they use our persistent version
|
|
window.setTheme = window.posSetTheme;
|
|
|
|
// Listen for system color scheme changes and auto-switch (only if user hasn't manually set a preference)
|
|
if (window.matchMedia) {
|
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
|
|
// Only auto-switch if user hasn't explicitly set a preference
|
|
var userExplicit = localStorage.getItem('pos_theme');
|
|
if (!userExplicit) {
|
|
var autoTheme = e.matches ? 'industrial' : 'modern';
|
|
document.documentElement.setAttribute('data-theme', autoTheme);
|
|
}
|
|
});
|
|
}
|
|
|
|
// ─── Expose globally ───
|
|
window.POS_USER = {
|
|
name: name,
|
|
role: role,
|
|
roleLabel: roleLabel,
|
|
initials: initials,
|
|
token: token,
|
|
tenantId: payload.tenant_id,
|
|
employeeId: payload.employee_id,
|
|
branchId: payload.branch_id,
|
|
permissions: payload.permissions || []
|
|
};
|
|
|
|
// ─── Page guard based on role + permissions ───
|
|
function isPageAllowed(pagePath, userRole, userPerms) {
|
|
if (userRole === 'owner' || userRole === 'admin') return true;
|
|
|
|
// Restricted roles (workshop/mechanic/counter/cashier) see modules based on permissions.
|
|
if (['workshop', 'mechanic', 'counter', 'cashier'].indexOf(userRole) !== -1) {
|
|
var allowed = [];
|
|
if (userRole === 'workshop' || userRole === 'mechanic') {
|
|
allowed = ['/pos/workshop'];
|
|
}
|
|
var permMap = {
|
|
'pos.sell': '/pos/sale',
|
|
'pos.view': '/pos/sale',
|
|
'catalog.view': '/pos/catalog',
|
|
'inventory.view': '/pos/inventory',
|
|
'customers.view': '/pos/customers',
|
|
'workshop.view': '/pos/workshop',
|
|
'pos.remission': '/pos/remission-notes',
|
|
'invoicing.view': '/pos/invoicing',
|
|
'quotations.view': '/pos/quotations',
|
|
'accounting.view': '/pos/accounting',
|
|
'reports.view': '/pos/reports',
|
|
'dashboard.view': '/pos/dashboard'
|
|
};
|
|
for (var p in permMap) {
|
|
if (userPerms.indexOf(p) !== -1 && allowed.indexOf(permMap[p]) === -1) {
|
|
allowed.push(permMap[p]);
|
|
}
|
|
}
|
|
return allowed.indexOf(pagePath) !== -1;
|
|
}
|
|
|
|
// Always allow login/logout pages so users can sign out without hitting the guard.
|
|
if (pagePath === '/pos/login' || pagePath === '/pos/logout') return true;
|
|
|
|
// Any other role (accountant, warehouse, sales, etc.) keeps the previous permissive behavior.
|
|
return true;
|
|
}
|
|
|
|
function enforcePageGuard(userRole, userPerms) {
|
|
if (isPageAllowed(path, userRole, userPerms)) return true;
|
|
// Build the actual list of allowed pages so we can pick a safe fallback.
|
|
var fallback = null;
|
|
if (userRole === 'owner' || userRole === 'admin') {
|
|
fallback = '/pos/dashboard';
|
|
} else if (['workshop', 'mechanic', 'counter', 'cashier'].indexOf(userRole) !== -1) {
|
|
var allowed = [];
|
|
if (userRole === 'workshop' || userRole === 'mechanic') allowed = ['/pos/workshop'];
|
|
var permMap = {
|
|
'pos.sell': '/pos/sale',
|
|
'pos.view': '/pos/sale',
|
|
'catalog.view': '/pos/catalog',
|
|
'inventory.view': '/pos/inventory',
|
|
'customers.view': '/pos/customers',
|
|
'workshop.view': '/pos/workshop',
|
|
'pos.remission': '/pos/remission-notes',
|
|
'invoicing.view': '/pos/invoicing',
|
|
'quotations.view': '/pos/quotations',
|
|
'accounting.view': '/pos/accounting',
|
|
'reports.view': '/pos/reports',
|
|
'dashboard.view': '/pos/dashboard'
|
|
};
|
|
for (var p in permMap) {
|
|
if (userPerms.indexOf(p) !== -1 && allowed.indexOf(permMap[p]) === -1) {
|
|
allowed.push(permMap[p]);
|
|
}
|
|
}
|
|
fallback = allowed.length ? allowed[0] : '/pos/login';
|
|
} else {
|
|
fallback = '/pos/dashboard';
|
|
}
|
|
window.location.replace(fallback);
|
|
return false;
|
|
}
|
|
|
|
// ─── Refresh permissions/token from server before enforcing the guard ───
|
|
// This makes permission changes effective without requiring a full re-login.
|
|
try {
|
|
fetch('/pos/api/auth/refresh', {
|
|
method: 'POST',
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
}).then(function(r) {
|
|
if (r.ok) return r.json();
|
|
return null;
|
|
}).then(function(data) {
|
|
if (data && data.token) {
|
|
localStorage.setItem('pos_token', data.token);
|
|
localStorage.setItem('pos_employee', JSON.stringify(data.employee));
|
|
token = data.token;
|
|
window.POS_USER.token = data.token;
|
|
window.POS_USER.permissions = data.permissions || [];
|
|
window.POS_USER.branchId = data.employee.branch_id;
|
|
}
|
|
if (!enforcePageGuard(window.POS_USER.role, window.POS_USER.permissions)) return;
|
|
if (typeof window.renderSidebar === 'function') {
|
|
window.renderSidebar(window.POS_USER.modules || JSON.parse(localStorage.getItem('pos_modules') || '{}'));
|
|
}
|
|
}).catch(function() {
|
|
enforcePageGuard(role, payload.permissions || []);
|
|
});
|
|
} catch(e) {
|
|
enforcePageGuard(role, payload.permissions || []);
|
|
}
|
|
|
|
// ─── Preload enabled modules for sidebar filtering ───
|
|
try {
|
|
fetch('/pos/api/config/modules', {
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
}).then(function(r) {
|
|
if (r.ok) return r.json();
|
|
}).then(function(data) {
|
|
if (data) {
|
|
localStorage.setItem('pos_modules', JSON.stringify(data));
|
|
window.POS_USER.modules = data;
|
|
if (typeof window.renderSidebar === 'function') {
|
|
window.renderSidebar(data);
|
|
}
|
|
}
|
|
}).catch(function() {});
|
|
} catch(e) {}
|
|
|
|
// ─── Hide POS "Sistema" button for roles that cannot access the dashboard ───
|
|
(function hideBackToSystemForRestrictedRoles() {
|
|
var backBtn = document.getElementById('backToSystemBtn');
|
|
if (!backBtn) return;
|
|
// owner/admin always see it; others only if they have dashboard.view.
|
|
if (role === 'owner' || role === 'admin') return;
|
|
if ((window.POS_USER.permissions || []).indexOf('dashboard.view') !== -1) return;
|
|
backBtn.style.display = 'none';
|
|
})();
|
|
|
|
// ─── Service Worker update handler ───
|
|
if ('serviceWorker' in navigator) {
|
|
navigator.serviceWorker.addEventListener('message', function (event) {
|
|
if (event.data && event.data.type === 'SW_UPDATED') {
|
|
console.log('[AppInit] SW updated to', event.data.cacheName, '— reloading...');
|
|
window.location.reload();
|
|
}
|
|
});
|
|
}
|
|
|
|
// ─── Global toast utility ───
|
|
window.toast = function(msg, type) {
|
|
type = type || 'success';
|
|
var bg = type === 'error' ? '#d32f2f' : '#388e3c';
|
|
var el = document.createElement('div');
|
|
el.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:99999;padding:12px 20px;border-radius:8px;background:' + bg + ';color:#fff;font-weight:500;box-shadow:0 4px 12px rgba(0,0,0,.2);opacity:0;transition:opacity .3s;';
|
|
el.textContent = msg;
|
|
document.body.appendChild(el);
|
|
// trigger reflow
|
|
el.offsetHeight;
|
|
el.style.opacity = '1';
|
|
setTimeout(function() {
|
|
el.style.opacity = '0';
|
|
setTimeout(function() { if (el.parentNode) el.parentNode.removeChild(el); }, 300);
|
|
}, 3000);
|
|
};
|
|
|
|
})();
|