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

@@ -188,6 +188,7 @@ async function loadDemos() {
<td><a href="https://${escapeHtml(d.subdomain)}.nexusautoparts.com.mx/pos/login" target="_blank" style="color:var(--accent)">${escapeHtml(d.subdomain)}</a></td>
<td>${d.demo_days_left !== null ? d.demo_days_left + " días" : "N/A"}</td>
<td>
<button class="btn-icon" onclick="openModulesModal(${d.id}, '${escapeHtml(d.name)}')" title="Módulos"><i class="fas fa-cubes" style="color:var(--accent)"></i></button>
<button class="btn-icon" onclick="resetTenant(${d.id})" title="Resetear"><i class="fas fa-undo"></i></button>
<button class="btn-icon" onclick="toggleTenant(${d.id}, ${!d.is_active})" title="${d.is_active ? "Desactivar" : "Activar"}"><i class="fas fa-${d.is_active ? "pause" : "play"}"></i></button>
<button class="btn-icon" onclick="confirmDelete(${d.id}, '${escapeHtml(d.name)}')" title="Eliminar"><i class="fas fa-trash" style="color:var(--danger)"></i></button>
@@ -254,6 +255,7 @@ async function loadTenants(withStats = false) {
<td>${t.is_active ? tag("Activo", "success") : tag("Inactivo", "danger")}</td>
<td>${formatDate(t.created_at)}</td>
<td>
<button class="btn-icon" onclick="openModulesModal(${t.id}, '${escapeHtml(t.name)}')" title="Módulos"><i class="fas fa-cubes" style="color:var(--accent)"></i></button>
<button class="btn-icon" onclick="resetTenant(${t.id})" title="Resetear datos"><i class="fas fa-undo"></i></button>
<button class="btn-icon" onclick="toggleTenant(${t.id}, ${!t.is_active})" title="${t.is_active ? "Desactivar" : "Activar"}"><i class="fas fa-${t.is_active ? "pause" : "play"}"></i></button>
<button class="btn-icon" onclick="confirmDelete(${t.id}, '${escapeHtml(t.name)}')" title="Eliminar"><i class="fas fa-trash" style="color:var(--danger)"></i></button>
@@ -475,5 +477,59 @@ function copyText(text) {
navigator.clipboard.writeText(text).then(() => toast("Copiado al portapapeles", "success"));
}
// ─── Modules ───────────────────────────────────────────────────────────────
let currentModulesTenantId = null;
async function openModulesModal(tenantId, name) {
currentModulesTenantId = tenantId;
document.getElementById("modules-modal-title").textContent = `Módulos — ${escapeHtml(name)}`;
document.getElementById("modules-modal").style.display = "flex";
// Load current state
const res = await api(`/api/tenants/${tenantId}/modules`);
if (res && res.status === 200) {
const m = res.data.data;
document.getElementById("mod-whatsapp").checked = m.whatsapp !== false;
document.getElementById("mod-marketplace").checked = m.marketplace !== false;
document.getElementById("mod-meli").checked = m.meli !== false;
} else {
toast("Error al cargar módulos", "error");
}
}
function closeModulesModal() {
document.getElementById("modules-modal").style.display = "none";
currentModulesTenantId = null;
}
async function saveModules() {
if (!currentModulesTenantId) return;
const btn = document.getElementById("modules-save-btn");
const originalText = btn.innerHTML;
btn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Guardando...`;
btn.disabled = true;
const payload = {
whatsapp: document.getElementById("mod-whatsapp").checked,
marketplace: document.getElementById("mod-marketplace").checked,
meli: document.getElementById("mod-meli").checked,
};
const res = await api(`/api/tenants/${currentModulesTenantId}/modules`, {
method: "PUT",
body: payload
});
if (res && res.status === 200) {
toast("Módulos actualizados", "success");
closeModulesModal();
} else {
toast(res?.data?.error || "Error al guardar", "error");
}
btn.innerHTML = originalText;
btn.disabled = false;
}
// ─── Init ──────────────────────────────────────────────────────────────────
document.addEventListener("DOMContentLoaded", initAuth);