chore(tenants): conteudo de publico/ atualizado

This commit is contained in:
deploy
2026-09-28 12:17:05 +00:00
parent fba859a542
commit c8d5411bcc
2 changed files with 314 additions and 4 deletions
+157 -2
View File
@@ -2415,6 +2415,20 @@ data:
.termo-acoes { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 10px; }
.erro-termo { color: var(--perigo); font-size: 13px; }
/* ── produtos e precos ───────────────────────────────────────────────────── */
.catalogo-tabela { width: 100%; border-collapse: collapse; font-size: 14px; }
.catalogo-tabela th, .catalogo-tabela td { padding: 8px 6px; border-bottom: 1px solid var(--line); text-align: left; }
.catalogo-tabela input[type="text"] {
width: 110px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 8px; font: inherit;
}
.catalogo-pacote { border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; margin: 12px 0; }
.catalogo-pacote h3 { margin: 0 0 8px; font-size: 15px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.catalogo-precos { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 8px; font-size: 14px; }
.catalogo-precos input { width: 120px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 8px; font: inherit; }
.catalogo-modulos { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 13px; }
.catalogo-acoes { display: flex; gap: 14px; align-items: center; margin: 4px 0 24px; }
.catalogo-acoes .erro { color: var(--perigo); }
painel.html: |
<!DOCTYPE html>
<html lang="pt-BR">
@@ -2439,7 +2453,7 @@ data:
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
-->
<link rel="stylesheet" href="/publico/fontes/fontes.css">
<link rel="stylesheet" href="/publico/painel.css?v=2.84">
<link rel="stylesheet" href="/publico/painel.css?v=2.85">
</head>
<body>
@@ -2499,6 +2513,7 @@ data:
<button type="button" class="aba" data-aba="receita" role="tab">Receita e carteira</button>
<button type="button" class="aba" data-aba="atividade" role="tab">Atividade</button>
<button type="button" class="aba" data-aba="termo" role="tab">Termo de dados</button>
<button type="button" class="aba" data-aba="catalogo" role="tab">Produtos e pre&ccedil;os</button>
</nav>
<!-- CLIENTES -->
@@ -2602,6 +2617,32 @@ data:
</div>
</section>
<!-- PRODUTOS E PRECOS: modulos avulsos e pacotes -->
<section id="aba-catalogo" class="painel-aba oculto">
<div class="cartao">
<h2>Pacotes</h2>
<p class="ajuda">
O pre&ccedil;o do pacote &eacute; o que a <b>vitrine e o checkout cobram</b> a partir do
momento em que voc&ecirc; salva. Assinatura j&aacute; aberta n&atilde;o muda. Pre&ccedil;o
vazio = vale o da configura&ccedil;&atilde;o (aparece em cinza). Pacote &agrave; venda
precisa de mensal e anual.
</p>
<div id="catalogoPacotes"><p class="ajuda">Abra esta aba para carregar.</p></div>
</div>
<div class="cartao">
<h2>M&oacute;dulos avulsos</h2>
<p class="ajuda">
Pre&ccedil;o de cada m&oacute;dulo vendido separado. <b>Ainda n&atilde;o aparece para o
cliente</b>: fica guardado para a loja de m&oacute;dulos e a troca de plano.
</p>
<div id="catalogoModulos"></div>
</div>
<div class="catalogo-acoes">
<button type="button" id="catalogoSalvar" class="btn">Salvar pre&ccedil;os</button>
<span id="catalogoResultado" class="ajuda"></span>
</div>
</section>
<!-- TERMO DE DADOS: o que autoriza ler o uso de dentro de cada sistema -->
<section id="aba-termo" class="painel-aba oculto">
<div class="cartao">
@@ -2629,7 +2670,7 @@ data:
</main>
<script src="/publico/painel.js?v=2.84"></script>
<script src="/publico/painel.js?v=2.85"></script>
</body>
</html>
@@ -2665,6 +2706,7 @@ data:
var API = "/api/plataforma/painel/visao";
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
var API_TERMO = "/api/plataforma/painel/termo-de-dados";
var API_CATALOGO = "/api/plataforma/painel/catalogo";
var CHAVE = "atm.painel.token";
var INTERVALO_SINO_MS = 60000;
@@ -3273,6 +3315,118 @@ data:
+ item("Treinos (30 dias)", numeroOuTraco(d.treinos30Dias));
}
/* ── produtos e precos ──────────────────────────────────────────────────── */
var catalogo = null;
function valorDoCampo(v) { return v === null || v === undefined ? "" : String(v); }
function campoPreco(tipo, codigo, ciclo, valor, configurado) {
// Texto, e nao `number`: o campo numerico descarta "189,90" em silencio, e
// virgula e como se digita dinheiro aqui.
return '<input type="text" inputmode="decimal" autocomplete="off" data-cat-' + tipo
+ '="' + esc(codigo) + '" data-ciclo="' + ciclo + '" value="' + esc(valorDoCampo(valor)) + '"'
+ (configurado ? ' placeholder="' + esc(Number(configurado).toFixed(2)) + '"' : ' placeholder="—"')
+ ' aria-label="' + ciclo + ' de ' + esc(codigo) + '">';
}
function pintarCatalogo() {
var modulos = catalogo.modulos || [];
$("catalogoPacotes").innerHTML = (catalogo.pacotes || []).map(function (p) {
var inclui = {};
(p.modulos || []).forEach(function (m) { inclui[m] = true; });
return '<div class="catalogo-pacote" data-pacote="' + esc(p.codigo) + '">'
+ "<h3>" + esc(p.nome)
+ '<label><input type="checkbox" data-cat-venda="' + esc(p.codigo) + '"' + (p.aVenda ? " checked" : "")
+ "> &agrave; venda</label>"
+ ((catalogo.naVitrine || []).indexOf(p.codigo) >= 0 ? '<span class="selo ok">na vitrine</span>' : "")
+ "</h3>"
+ '<div class="catalogo-precos"><label>Mensal (R$) ' + campoPreco("pacote", p.codigo, "mensal", p.mensal, p.mensalConfigurado)
+ '</label><label>Anual (R$) ' + campoPreco("pacote", p.codigo, "anual", p.anual, p.anualConfigurado) + "</label></div>"
+ '<div class="catalogo-modulos">' + modulos.map(function (m) {
return '<label><input type="checkbox" data-cat-inclui="' + esc(p.codigo) + '" value="' + esc(m.codigo) + '"'
+ (inclui[m.codigo] ? " checked" : "") + "> " + esc(m.nome) + "</label>";
}).join("") + "</div></div>";
}).join("");
$("catalogoModulos").innerHTML = '<table class="catalogo-tabela"><thead><tr><th>M&oacute;dulo</th>'
+ "<th>Vende avulso</th><th>Mensal (R$)</th><th>Anual (R$)</th></tr></thead><tbody>"
+ modulos.map(function (m) {
return "<tr><td>" + esc(m.nome) + "</td>"
+ '<td><input type="checkbox" data-cat-venda="' + esc(m.codigo) + '"' + (m.aVenda ? " checked" : "") + "></td>"
+ "<td>" + campoPreco("modulo", m.codigo, "mensal", m.mensal, null) + "</td>"
+ "<td>" + campoPreco("modulo", m.codigo, "anual", m.anual, null) + "</td></tr>";
}).join("") + "</tbody></table>";
}
function lerPreco(tipo, codigo, ciclo) {
var el = document.querySelector('[data-cat-' + tipo + '="' + codigo + '"][data-ciclo="' + ciclo + '"]');
var v = el ? el.value.trim().replace(/\s|R\$/g, "").replace(/\.(?=\d{3}(\D|$))/g, "").replace(",", ".") : "";
if (v === "") return null;
var n = Number(v);
if (!isFinite(n) || n <= 0) throw new Error("Preço inválido: \"" + el.value + "\" (use, por exemplo, 189,90).");
return Math.round(n * 100) / 100;
}
function pedidoDoCatalogo() {
var marcado = function (codigo) {
var el = document.querySelector('[data-cat-venda="' + codigo + '"]');
return !!(el && el.checked);
};
return {
modulos: (catalogo.modulos || []).map(function (m) {
return { codigo: m.codigo, mensal: lerPreco("modulo", m.codigo, "mensal"),
anual: lerPreco("modulo", m.codigo, "anual"), aVenda: marcado(m.codigo) };
}),
pacotes: (catalogo.pacotes || []).map(function (p) {
var modulos = [];
document.querySelectorAll('[data-cat-inclui="' + p.codigo + '"]').forEach(function (c) {
if (c.checked) modulos.push(c.value);
});
return { codigo: p.codigo, mensal: lerPreco("pacote", p.codigo, "mensal"),
anual: lerPreco("pacote", p.codigo, "anual"), aVenda: marcado(p.codigo), modulos: modulos };
})
};
}
function pedirCatalogo(metodo, corpo) {
var h = { "x-atm-admin-token": tokenAtual };
if (corpo) h["Content-Type"] = "application/json";
return fetch(API_CATALOGO, { method: metodo, headers: h, body: corpo ? JSON.stringify(corpo) : undefined })
.then(function (r) {
if (r.ok) return r.json();
return r.json().catch(function () { return {}; }).then(function (d) {
throw new Error(d.erro || "HTTP " + r.status);
});
});
}
function carregarCatalogo() {
if (!tokenAtual) return Promise.resolve();
return pedirCatalogo("GET").then(function (c) { catalogo = c || {}; pintarCatalogo(); })
.catch(function (e) {
$("catalogoPacotes").innerHTML = '<p class="ajuda">Não consegui carregar: ' + esc(e.message) + "</p>";
});
}
$("catalogoSalvar").addEventListener("click", function () {
if (!catalogo) return;
var botao = $("catalogoSalvar"), saida = $("catalogoResultado");
botao.disabled = true;
saida.classList.remove("erro");
saida.textContent = "Salvando…";
var pedido;
try { pedido = pedidoDoCatalogo(); } catch (e) {
saida.classList.add("erro"); saida.textContent = e.message; botao.disabled = false; return;
}
pedirCatalogo("PUT", pedido).then(function (c) {
catalogo = c; pintarCatalogo();
saida.textContent = "Salvo. Na vitrine agora: " + ((c.naVitrine || []).join(", ") || "nenhum pacote") + ".";
}).catch(function (e) {
saida.classList.add("erro");
saida.textContent = e.message;
}).then(function () { botao.disabled = false; });
});
/* ── termo de dados ─────────────────────────────────────────────────────── */
function pedirTermo(metodo, caminho, corpo) {
@@ -3446,6 +3600,7 @@ data:
p.classList.toggle("oculto", p.id !== "aba-" + b.getAttribute("data-aba"));
});
if (b.getAttribute("data-aba") === "termo") carregarTermo();
if (b.getAttribute("data-aba") === "catalogo") carregarCatalogo();
});
});
+157 -2
View File
@@ -2415,6 +2415,20 @@ data:
.termo-acoes { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 10px; }
.erro-termo { color: var(--perigo); font-size: 13px; }
/* ── produtos e precos ───────────────────────────────────────────────────── */
.catalogo-tabela { width: 100%; border-collapse: collapse; font-size: 14px; }
.catalogo-tabela th, .catalogo-tabela td { padding: 8px 6px; border-bottom: 1px solid var(--line); text-align: left; }
.catalogo-tabela input[type="text"] {
width: 110px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 8px; font: inherit;
}
.catalogo-pacote { border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; margin: 12px 0; }
.catalogo-pacote h3 { margin: 0 0 8px; font-size: 15px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.catalogo-precos { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 8px; font-size: 14px; }
.catalogo-precos input { width: 120px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 8px; font: inherit; }
.catalogo-modulos { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 13px; }
.catalogo-acoes { display: flex; gap: 14px; align-items: center; margin: 4px 0 24px; }
.catalogo-acoes .erro { color: var(--perigo); }
painel.html: |
<!DOCTYPE html>
<html lang="pt-BR">
@@ -2439,7 +2453,7 @@ data:
`Cache-Control: immutable` por um ano. Suba o numero a cada publicacao.
-->
<link rel="stylesheet" href="/publico/fontes/fontes.css">
<link rel="stylesheet" href="/publico/painel.css?v=2.84">
<link rel="stylesheet" href="/publico/painel.css?v=2.85">
</head>
<body>
@@ -2499,6 +2513,7 @@ data:
<button type="button" class="aba" data-aba="receita" role="tab">Receita e carteira</button>
<button type="button" class="aba" data-aba="atividade" role="tab">Atividade</button>
<button type="button" class="aba" data-aba="termo" role="tab">Termo de dados</button>
<button type="button" class="aba" data-aba="catalogo" role="tab">Produtos e pre&ccedil;os</button>
</nav>
<!-- CLIENTES -->
@@ -2602,6 +2617,32 @@ data:
</div>
</section>
<!-- PRODUTOS E PRECOS: modulos avulsos e pacotes -->
<section id="aba-catalogo" class="painel-aba oculto">
<div class="cartao">
<h2>Pacotes</h2>
<p class="ajuda">
O pre&ccedil;o do pacote &eacute; o que a <b>vitrine e o checkout cobram</b> a partir do
momento em que voc&ecirc; salva. Assinatura j&aacute; aberta n&atilde;o muda. Pre&ccedil;o
vazio = vale o da configura&ccedil;&atilde;o (aparece em cinza). Pacote &agrave; venda
precisa de mensal e anual.
</p>
<div id="catalogoPacotes"><p class="ajuda">Abra esta aba para carregar.</p></div>
</div>
<div class="cartao">
<h2>M&oacute;dulos avulsos</h2>
<p class="ajuda">
Pre&ccedil;o de cada m&oacute;dulo vendido separado. <b>Ainda n&atilde;o aparece para o
cliente</b>: fica guardado para a loja de m&oacute;dulos e a troca de plano.
</p>
<div id="catalogoModulos"></div>
</div>
<div class="catalogo-acoes">
<button type="button" id="catalogoSalvar" class="btn">Salvar pre&ccedil;os</button>
<span id="catalogoResultado" class="ajuda"></span>
</div>
</section>
<!-- TERMO DE DADOS: o que autoriza ler o uso de dentro de cada sistema -->
<section id="aba-termo" class="painel-aba oculto">
<div class="cartao">
@@ -2629,7 +2670,7 @@ data:
</main>
<script src="/publico/painel.js?v=2.84"></script>
<script src="/publico/painel.js?v=2.85"></script>
</body>
</html>
@@ -2665,6 +2706,7 @@ data:
var API = "/api/plataforma/painel/visao";
var API_ALERTAS = "/api/plataforma/painel/alertas-infra";
var API_TERMO = "/api/plataforma/painel/termo-de-dados";
var API_CATALOGO = "/api/plataforma/painel/catalogo";
var CHAVE = "atm.painel.token";
var INTERVALO_SINO_MS = 60000;
@@ -3273,6 +3315,118 @@ data:
+ item("Treinos (30 dias)", numeroOuTraco(d.treinos30Dias));
}
/* ── produtos e precos ──────────────────────────────────────────────────── */
var catalogo = null;
function valorDoCampo(v) { return v === null || v === undefined ? "" : String(v); }
function campoPreco(tipo, codigo, ciclo, valor, configurado) {
// Texto, e nao `number`: o campo numerico descarta "189,90" em silencio, e
// virgula e como se digita dinheiro aqui.
return '<input type="text" inputmode="decimal" autocomplete="off" data-cat-' + tipo
+ '="' + esc(codigo) + '" data-ciclo="' + ciclo + '" value="' + esc(valorDoCampo(valor)) + '"'
+ (configurado ? ' placeholder="' + esc(Number(configurado).toFixed(2)) + '"' : ' placeholder="—"')
+ ' aria-label="' + ciclo + ' de ' + esc(codigo) + '">';
}
function pintarCatalogo() {
var modulos = catalogo.modulos || [];
$("catalogoPacotes").innerHTML = (catalogo.pacotes || []).map(function (p) {
var inclui = {};
(p.modulos || []).forEach(function (m) { inclui[m] = true; });
return '<div class="catalogo-pacote" data-pacote="' + esc(p.codigo) + '">'
+ "<h3>" + esc(p.nome)
+ '<label><input type="checkbox" data-cat-venda="' + esc(p.codigo) + '"' + (p.aVenda ? " checked" : "")
+ "> &agrave; venda</label>"
+ ((catalogo.naVitrine || []).indexOf(p.codigo) >= 0 ? '<span class="selo ok">na vitrine</span>' : "")
+ "</h3>"
+ '<div class="catalogo-precos"><label>Mensal (R$) ' + campoPreco("pacote", p.codigo, "mensal", p.mensal, p.mensalConfigurado)
+ '</label><label>Anual (R$) ' + campoPreco("pacote", p.codigo, "anual", p.anual, p.anualConfigurado) + "</label></div>"
+ '<div class="catalogo-modulos">' + modulos.map(function (m) {
return '<label><input type="checkbox" data-cat-inclui="' + esc(p.codigo) + '" value="' + esc(m.codigo) + '"'
+ (inclui[m.codigo] ? " checked" : "") + "> " + esc(m.nome) + "</label>";
}).join("") + "</div></div>";
}).join("");
$("catalogoModulos").innerHTML = '<table class="catalogo-tabela"><thead><tr><th>M&oacute;dulo</th>'
+ "<th>Vende avulso</th><th>Mensal (R$)</th><th>Anual (R$)</th></tr></thead><tbody>"
+ modulos.map(function (m) {
return "<tr><td>" + esc(m.nome) + "</td>"
+ '<td><input type="checkbox" data-cat-venda="' + esc(m.codigo) + '"' + (m.aVenda ? " checked" : "") + "></td>"
+ "<td>" + campoPreco("modulo", m.codigo, "mensal", m.mensal, null) + "</td>"
+ "<td>" + campoPreco("modulo", m.codigo, "anual", m.anual, null) + "</td></tr>";
}).join("") + "</tbody></table>";
}
function lerPreco(tipo, codigo, ciclo) {
var el = document.querySelector('[data-cat-' + tipo + '="' + codigo + '"][data-ciclo="' + ciclo + '"]');
var v = el ? el.value.trim().replace(/\s|R\$/g, "").replace(/\.(?=\d{3}(\D|$))/g, "").replace(",", ".") : "";
if (v === "") return null;
var n = Number(v);
if (!isFinite(n) || n <= 0) throw new Error("Preço inválido: \"" + el.value + "\" (use, por exemplo, 189,90).");
return Math.round(n * 100) / 100;
}
function pedidoDoCatalogo() {
var marcado = function (codigo) {
var el = document.querySelector('[data-cat-venda="' + codigo + '"]');
return !!(el && el.checked);
};
return {
modulos: (catalogo.modulos || []).map(function (m) {
return { codigo: m.codigo, mensal: lerPreco("modulo", m.codigo, "mensal"),
anual: lerPreco("modulo", m.codigo, "anual"), aVenda: marcado(m.codigo) };
}),
pacotes: (catalogo.pacotes || []).map(function (p) {
var modulos = [];
document.querySelectorAll('[data-cat-inclui="' + p.codigo + '"]').forEach(function (c) {
if (c.checked) modulos.push(c.value);
});
return { codigo: p.codigo, mensal: lerPreco("pacote", p.codigo, "mensal"),
anual: lerPreco("pacote", p.codigo, "anual"), aVenda: marcado(p.codigo), modulos: modulos };
})
};
}
function pedirCatalogo(metodo, corpo) {
var h = { "x-atm-admin-token": tokenAtual };
if (corpo) h["Content-Type"] = "application/json";
return fetch(API_CATALOGO, { method: metodo, headers: h, body: corpo ? JSON.stringify(corpo) : undefined })
.then(function (r) {
if (r.ok) return r.json();
return r.json().catch(function () { return {}; }).then(function (d) {
throw new Error(d.erro || "HTTP " + r.status);
});
});
}
function carregarCatalogo() {
if (!tokenAtual) return Promise.resolve();
return pedirCatalogo("GET").then(function (c) { catalogo = c || {}; pintarCatalogo(); })
.catch(function (e) {
$("catalogoPacotes").innerHTML = '<p class="ajuda">Não consegui carregar: ' + esc(e.message) + "</p>";
});
}
$("catalogoSalvar").addEventListener("click", function () {
if (!catalogo) return;
var botao = $("catalogoSalvar"), saida = $("catalogoResultado");
botao.disabled = true;
saida.classList.remove("erro");
saida.textContent = "Salvando…";
var pedido;
try { pedido = pedidoDoCatalogo(); } catch (e) {
saida.classList.add("erro"); saida.textContent = e.message; botao.disabled = false; return;
}
pedirCatalogo("PUT", pedido).then(function (c) {
catalogo = c; pintarCatalogo();
saida.textContent = "Salvo. Na vitrine agora: " + ((c.naVitrine || []).join(", ") || "nenhum pacote") + ".";
}).catch(function (e) {
saida.classList.add("erro");
saida.textContent = e.message;
}).then(function () { botao.disabled = false; });
});
/* ── termo de dados ─────────────────────────────────────────────────────── */
function pedirTermo(metodo, caminho, corpo) {
@@ -3446,6 +3600,7 @@ data:
p.classList.toggle("oculto", p.id !== "aba-" + b.getAttribute("data-aba"));
});
if (b.getAttribute("data-aba") === "termo") carregarTermo();
if (b.getAttribute("data-aba") === "catalogo") carregarCatalogo();
});
});