feat: module toggles in POS config and Instance Manager

- Add GET/PUT /pos/api/config/modules endpoints in POS config_bp.py
- Update sidebar.js to filter nav items based on enabled modules
- Add Modules section to POS config.html with toggles for WhatsApp, Marketplace, MercadoLibre
- Add module load/save logic to POS config.js
- Preload modules in app-init.js for sidebar caching

- Add tenant module management to Instance Manager
  - get_tenant_modules / update_tenant_modules in tenant_service.py
  - GET/PUT /api/tenants/<id>/modules endpoints in tenants_bp.py
  - Add modules modal to manager index.html
  - Add module editing UI and logic to manager.js
  - Add toggle-switch CSS to manager.css
This commit is contained in:
2026-05-28 00:21:52 +00:00
parent 999591e248
commit 718fa06888
26 changed files with 2614 additions and 429 deletions

View File

@@ -121,6 +121,9 @@ function showSection(sectionId) {
case 'users':
loadUsers();
break;
case 'tenants':
loadTenants();
break;
}
}
@@ -2074,3 +2077,99 @@ async function toggleUserActive(userId, currentActive) {
showAlert(e.message, 'error');
}
}
// ─── Tenants / Modules ─────────────────────────────────────────────────────
async function loadTenants() {
var token = localStorage.getItem('access_token');
var tbody = document.getElementById('tenantsTable');
tbody.innerHTML = '<tr><td colspan="6" class="loading"><div class="spinner"></div></td></tr>';
try {
var res = await fetch('/api/admin/tenants', {
headers: { 'Authorization': 'Bearer ' + token }
});
if (!res.ok) throw new Error('Error al cargar tenants (' + res.status + ')');
var data = await res.json();
var tenants = data.tenants || [];
if (tenants.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center; color:var(--text-secondary); padding:2rem;">No hay tenants activos</td></tr>';
return;
}
// Load modules for each tenant
var modulesMap = {};
await Promise.all(tenants.map(async function(t) {
try {
var mres = await fetch('/api/admin/tenants/' + t.id + '/modules', {
headers: { 'Authorization': 'Bearer ' + token }
});
if (mres.ok) {
modulesMap[t.id] = await mres.json();
} else {
modulesMap[t.id] = {};
}
} catch (e) {
modulesMap[t.id] = {};
}
}));
renderTenantsTable(tenants, modulesMap);
} catch (e) {
tbody.innerHTML = '<tr><td colspan="6" style="text-align:center; color:#ef4444; padding:2rem;">' + e.message + '</td></tr>';
}
}
function renderTenantsTable(tenants, modulesMap) {
var tbody = document.getElementById('tenantsTable');
tbody.innerHTML = tenants.map(function(t) {
var mods = modulesMap[t.id] || {};
function toggleBtn(tenantId, key, enabled) {
var label = enabled ? 'Activado' : 'Desactivado';
var cls = enabled ? 'btn-primary' : 'btn-secondary';
return '<button class="btn ' + cls + '" style="font-size:0.75rem; padding:3px 10px;" ' +
'onclick="toggleTenantModule(' + tenantId + ', \'' + key + '\', ' + enabled + ')">' + label + '</button>';
}
return '<tr>' +
'<td>' + t.id + '</td>' +
'<td>' + (t.name || '-') + '</td>' +
'<td>' + toggleBtn(t.id, 'whatsapp_enabled', !!mods.whatsapp_enabled) + '</td>' +
'<td>' + toggleBtn(t.id, 'marketplace_enabled', !!mods.marketplace_enabled) + '</td>' +
'<td>' + toggleBtn(t.id, 'meli_enabled', !!mods.meli_enabled) + '</td>' +
'<td><button class="btn btn-primary" style="font-size:0.75rem; padding:3px 10px;" onclick="loadTenants()">🔄 Recargar</button></td>' +
'</tr>';
}).join('');
}
async function toggleTenantModule(tenantId, key, currentValue) {
var token = localStorage.getItem('access_token');
var moduleNames = {
'whatsapp_enabled': 'WhatsApp',
'marketplace_enabled': 'Marketplace',
'meli_enabled': 'MercadoLibre'
};
var action = currentValue ? 'desactivar' : 'activar';
if (!confirm('¿Seguro que deseas ' + action + ' ' + moduleNames[key] + ' para el tenant #' + tenantId + '?')) return;
try {
var payload = {};
payload[key] = !currentValue;
var res = await fetch('/api/admin/tenants/' + tenantId + '/modules', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify(payload)
});
if (!res.ok) {
var err = await res.json();
throw new Error(err.error || 'Error al actualizar módulo');
}
showAlert(moduleNames[key] + ' ' + (currentValue ? 'desactivado' : 'activado') + ' para tenant #' + tenantId);
loadTenants();
} catch (e) {
showAlert(e.message, 'error');
}
}