diff --git a/src/main/java/com/seconectai/condocontab/master/http/MasterHandlers.java b/src/main/java/com/seconectai/condocontab/master/http/MasterHandlers.java index bdf774e..0e0d105 100644 --- a/src/main/java/com/seconectai/condocontab/master/http/MasterHandlers.java +++ b/src/main/java/com/seconectai/condocontab/master/http/MasterHandlers.java @@ -397,7 +397,7 @@ public final class MasterHandlers implements HttpHandler { String dbName = tenantProvisioner.databaseNameFromSlug(slug); try (Connection conn = dataSourceProvider.getMasterConnection()) { if (exists(conn, "SELECT 1 FROM empresas WHERE slug = ?", slug.replace('-', '_'))) { - HttpResponses.json(ex, 409, Map.of("erro", "slug_duplicado")); + HttpResponses.json(ex, 409, Map.of("erro", "slug_duplicado", "mensagem", "Este Slug ja esta cadastrado em outro cliente.")); return; } if (exists(conn, "SELECT 1 FROM funcionarios WHERE email = ?", emailPrincipal)) { diff --git a/src/main/webapp/css/app.css b/src/main/webapp/css/app.css index 5339885..0c1c462 100644 --- a/src/main/webapp/css/app.css +++ b/src/main/webapp/css/app.css @@ -633,21 +633,23 @@ th { position: fixed; left: 50%; transform: translateX(-50%); - z-index: 20; + z-index: 1000; max-width: min(720px, calc(100vw - 24px)); border-radius: var(--radius); - padding: 12px 14px; - box-shadow: var(--shadow-md); + padding: 12px 18px; + box-shadow: var(--shadow-lg); + font-weight: 600; } .toast { - bottom: 18px; + bottom: 24px; background: #17202a; color: #fff; } .toast.error { background: var(--danger); + border: 1px solid rgba(255, 255, 255, .2); } .finance-banner { diff --git a/src/main/webapp/master/clientes.html b/src/main/webapp/master/clientes.html index 8afacd4..e47d5f5 100644 --- a/src/main/webapp/master/clientes.html +++ b/src/main/webapp/master/clientes.html @@ -57,6 +57,7 @@
+
diff --git a/src/main/webapp/master/clientes.js b/src/main/webapp/master/clientes.js index 06190bf..60fbc49 100644 --- a/src/main/webapp/master/clientes.js +++ b/src/main/webapp/master/clientes.js @@ -16,12 +16,22 @@ function statusAction(e){const acao=e.status==='ativa'?'Bloquear':'Ativar';retur function detalheRow(e){if(selecionado!==e.id||!modo)return'';if(modo==='ver')return `

${esc(e.nome)}

Slug: ${esc(e.slug)}

CNPJ: ${esc(e.cnpj||'-')}

Status: ${esc(e.status)}

Plano: ${e.plano?`${esc(e.plano)} ${e.plano_valor_centavos?'- '+Util.money(e.plano_valor_centavos):''}`:'-'}

Conta principal: abra Editar para alterar dados da empresa; funcionarios ficam no acesso da empresa.

`;return `
`} function proximoSlugSequencial(){ - let max=0; - for(const e of empresas){ - const n=parseInt(e.slug,10); - if(!isNaN(n)&&n>max)max=n; + const slugsExistentes = new Set(empresas.map(e => String(e.slug || '').trim().toLowerCase())); + let max = 0; + for (const e of empresas) { + const s = String(e.slug || '').trim(); + const n = parseInt(s, 10); + if (!isNaN(n) && /^\d+$/.test(s)) { + if (n > max) max = n; + } } - return String(max+1).padStart(4,'0'); + let proximo = max + 1; + let candidato = String(proximo).padStart(4, '0'); + while (slugsExistentes.has(candidato.toLowerCase())) { + proximo++; + candidato = String(proximo).padStart(4, '0'); + } + return candidato; } function slugify(text){ @@ -34,6 +44,25 @@ function slugify(text){ .replace(/^_+|_+$/g,''); } +function mostrarErroModal(msg){ + const el = document.querySelector('#modal-error-msg'); + if(el){ + el.textContent = msg; + el.classList.remove('hidden'); + el.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } +} + +function limparErroModal(){ + const el = document.querySelector('#modal-error-msg'); + if(el){ + el.textContent = ''; + el.classList.add('hidden'); + } + const slugInput = empresaForm.querySelector('input[name="slug"]'); + if(slugInput) slugInput.style.borderColor = ''; +} + function render(){ const q=busca.value.trim(); const lista=empresas.filter(e=>match(e,q)); @@ -67,6 +96,7 @@ window.alternarTipoSlug=()=>{ const slugInput=empresaForm.querySelector('input[name="slug"]'); const nomeInput=empresaForm.querySelector('input[name="nome"]'); const btn=document.querySelector('#btn-slug-tipo'); + limparErroModal(); if(modoSlugAuto==='sequencial'){ modoSlugAuto='nome'; if(btn)btn.textContent='Usar sequencial'; @@ -79,6 +109,7 @@ window.alternarTipoSlug=()=>{ }; window.abrirModalCadastro=()=>{ + limparErroModal(); cadastroModal?.classList.remove('hidden'); const slugInput=empresaForm.querySelector('input[name="slug"]'); const btn=document.querySelector('#btn-slug-tipo'); @@ -86,11 +117,13 @@ window.abrirModalCadastro=()=>{ if(btn)btn.textContent='Gerar do nome'; if(slugInput){ slugInput.value=proximoSlugSequencial(); + slugInput.style.borderColor=''; } empresaForm.querySelector('input[name="nome"]')?.focus(); }; window.fecharModalCadastro=()=>{ + limparErroModal(); cadastroModal?.classList.add('hidden'); }; @@ -112,20 +145,49 @@ nomeInput?.addEventListener('input',()=>{ } }); +empresaForm.querySelector('input[name="slug"]')?.addEventListener('input',()=>{ + limparErroModal(); +}); + empresaForm.onsubmit=async e=>{ e.preventDefault(); + limparErroModal(); const slugInput=empresaForm.querySelector('input[name="slug"]'); - if(slugInput&&!slugInput.value.trim()){ + if(!slugInput) return; + + if(!slugInput.value.trim()){ slugInput.value=proximoSlugSequencial(); } + + const slugValor = slugInput.value.trim().toLowerCase(); + const duplicado = empresas.find(emp => String(emp.slug || '').trim().toLowerCase() === slugValor); + if(duplicado){ + const msg = `O Slug "${slugInput.value}" já está cadastrado no cliente "${duplicado.nome}". Por favor, utilize o próximo código sugerido (${proximoSlugSequencial()}).`; + mostrarErroModal(msg); + slugInput.style.borderColor = 'var(--danger)'; + slugInput.focus(); + Util.toast(msg, true); + return; + } + try{ await Api.post('/api/master/empresas',Util.formData(empresaForm)); empresaForm.reset(); fecharModalCadastro(); - Util.toast('Cliente cadastrado'); - load(); + Util.toast('Cliente cadastrado com sucesso!'); + await load(); }catch(err){ - Util.toast(err.message,true); + let msg = err.message || 'Erro ao cadastrar cliente'; + if(msg.includes('slug_duplicado')){ + msg = `O Slug "${slugInput.value}" já está em uso por outro cliente. Altere para outro código ou clique em "Usar sequencial".`; + slugInput.style.borderColor = 'var(--danger)'; + slugInput.focus(); + }else if(msg.includes('email_duplicado')){ + msg = 'Este e-mail de login já está cadastrado no sistema.'; + empresaForm.querySelector('input[name="email"]')?.focus(); + } + mostrarErroModal(msg); + Util.toast(msg, true); } };