feat: add dynamic field toggles, phone/cpf/cep input masks, and auto-textarea promotion for observations in public pre-matricula
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m1s
Details
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m1s
Details
This commit is contained in:
parent
9c1e604cb9
commit
b97a15544c
|
|
@ -244,6 +244,30 @@ const PreMatricula: React.FC<Props> = ({ data, onConvert }) => {
|
||||||
<span className="text-xs font-mono text-slate-500">{config?.corPrimaria}</span>
|
<span className="text-xs font-mono text-slate-500">{config?.corPrimaria}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="block text-[10px] font-bold text-slate-500 uppercase">Campos Padrão do Formulário</label>
|
||||||
|
<div className="bg-slate-50 border border-slate-200 rounded-lg p-3 space-y-2">
|
||||||
|
<label className="flex items-center gap-2 text-xs font-bold text-slate-700 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500"
|
||||||
|
checked={config?.exibirEmail !== false}
|
||||||
|
onChange={(e) => setConfig(prev => prev ? { ...prev, exibirEmail: e.target.checked } : prev)}
|
||||||
|
/>
|
||||||
|
Exibir Campo E-mail
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-2 text-xs font-bold text-slate-700 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="w-4 h-4 text-indigo-600 rounded border-slate-300 focus:ring-indigo-500"
|
||||||
|
checked={config?.exibirTelefone !== false}
|
||||||
|
onChange={(e) => setConfig(prev => prev ? { ...prev, exibirTelefone: e.target.checked } : prev)}
|
||||||
|
/>
|
||||||
|
Exibir Campo Telefone / WhatsApp
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-slate-400 font-medium">Nome Completo é sempre obrigatório para identificar o interessado.</p>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-2">Turmas Disponíveis no Formulário</label>
|
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-2">Turmas Disponíveis no Formulário</label>
|
||||||
<div className="bg-slate-50 border border-slate-200 rounded-lg p-3 max-h-40 overflow-y-auto space-y-1">
|
<div className="bg-slate-50 border border-slate-200 rounded-lg p-3 max-h-40 overflow-y-auto space-y-1">
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,18 @@ async function run() {
|
||||||
try {
|
try {
|
||||||
await client.query(`
|
await client.query(`
|
||||||
ALTER TABLE prematricula_config
|
ALTER TABLE prematricula_config
|
||||||
ADD COLUMN IF NOT EXISTS turmas_permitidas TEXT[] DEFAULT '{}';
|
ADD COLUMN IF NOT EXISTS exibir_email BOOLEAN DEFAULT TRUE;
|
||||||
|
|
||||||
|
ALTER TABLE prematricula_config
|
||||||
|
ADD COLUMN IF NOT EXISTS exibir_telefone BOOLEAN DEFAULT TRUE;
|
||||||
|
|
||||||
|
UPDATE prematricula_config
|
||||||
|
SET exibir_email = TRUE, exibir_telefone = TRUE
|
||||||
|
WHERE exibir_email IS NULL OR exibir_telefone IS NULL;
|
||||||
`);
|
`);
|
||||||
console.log('✅ Coluna turmas_permitidas adicionada com sucesso!');
|
console.log('✅ Colunas exibir_email e exibir_telefone adicionadas com sucesso!');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('❌ Erro ao adicionar coluna:', e.message);
|
console.error('❌ Erro ao adicionar colunas:', e.message);
|
||||||
} finally {
|
} finally {
|
||||||
client.release();
|
client.release();
|
||||||
await pool.end();
|
await pool.end();
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,38 @@ body{font-family:'Inter',sans-serif;background:#f8fafc;min-height:100vh;display:
|
||||||
<script>
|
<script>
|
||||||
const slug='${slug}';
|
const slug='${slug}';
|
||||||
const app=document.getElementById('app');
|
const app=document.getElementById('app');
|
||||||
|
function maskPhone(v){
|
||||||
|
if(!v)return "";
|
||||||
|
v=v.replace(/\D/g,"");
|
||||||
|
v=v.replace(/(\d{2})(\d)/,"($1) $2");
|
||||||
|
v=v.replace(/(\d{5})(\d)/,"$1-$2");
|
||||||
|
return v.substring(0,15);
|
||||||
|
}
|
||||||
|
function maskCPF(v){
|
||||||
|
if(!v)return "";
|
||||||
|
v=v.replace(/\D/g,"");
|
||||||
|
v=v.replace(/(\d{3})(\d)/,"$1.$2");
|
||||||
|
v=v.replace(/(\d{3})(\d)/,"$1.$2");
|
||||||
|
v=v.replace(/(\d{3})(\d{1,2})$/,"$1-$2");
|
||||||
|
return v.substring(0,14);
|
||||||
|
}
|
||||||
|
function maskCEP(v){
|
||||||
|
if(!v)return "";
|
||||||
|
v=v.replace(/\D/g,"");
|
||||||
|
v=v.replace(/(\d{5})(\d)/,"$1-$2");
|
||||||
|
return v.substring(0,9);
|
||||||
|
}
|
||||||
|
document.addEventListener('input',function(e){
|
||||||
|
if(!e.target)return;
|
||||||
|
const dt=e.target.getAttribute('data-type');
|
||||||
|
if(dt==='phone'){
|
||||||
|
e.target.value=maskPhone(e.target.value);
|
||||||
|
}else if(dt==='cpf'){
|
||||||
|
e.target.value=maskCPF(e.target.value);
|
||||||
|
}else if(dt==='cep'){
|
||||||
|
e.target.value=maskCEP(e.target.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
async function init(){
|
async function init(){
|
||||||
try{
|
try{
|
||||||
const res=await fetch('/api/prematricula/public/'+slug);
|
const res=await fetch('/api/prematricula/public/'+slug);
|
||||||
|
|
@ -120,8 +152,12 @@ async function init(){
|
||||||
html+='<h1>'+c.titulo+'</h1><p>'+c.descricao+'</p></div>';
|
html+='<h1>'+c.titulo+'</h1><p>'+c.descricao+'</p></div>';
|
||||||
html+='<form class="form" id="preForm" onsubmit="return submitForm(event)">';
|
html+='<form class="form" id="preForm" onsubmit="return submitForm(event)">';
|
||||||
html+='<div class="field"><label>Nome Completo <span class="req">*</span></label><input name="_nome" required placeholder="Seu nome completo"></div>';
|
html+='<div class="field"><label>Nome Completo <span class="req">*</span></label><input name="_nome" required placeholder="Seu nome completo"></div>';
|
||||||
html+='<div class="field"><label>Email</label><input name="_email" type="email" placeholder="seu@email.com"></div>';
|
if(c.exibirEmail !== false){
|
||||||
html+='<div class="field"><label>Telefone / WhatsApp</label><input name="_telefone" placeholder="(00) 00000-0000"></div>';
|
html+='<div class="field"><label>Email</label><input name="_email" type="email" placeholder="seu@email.com"></div>';
|
||||||
|
}
|
||||||
|
if(c.exibirTelefone !== false){
|
||||||
|
html+='<div class="field"><label>Telefone / WhatsApp</label><input name="_telefone" data-type="phone" placeholder="(00) 00000-0000"></div>';
|
||||||
|
}
|
||||||
if(data.turmas.length>0){
|
if(data.turmas.length>0){
|
||||||
html+='<div class="field"><label>Turma de Interesse <span class="req">*</span></label><select name="_turma" required><option value="">Selecione a turma...</option>';
|
html+='<div class="field"><label>Turma de Interesse <span class="req">*</span></label><select name="_turma" required><option value="">Selecione a turma...</option>';
|
||||||
data.turmas.forEach(t=>{html+='<option value="'+t.id+'" data-nome="'+t.nome+'">'+t.nome+'</option>'});
|
data.turmas.forEach(t=>{html+='<option value="'+t.id+'" data-nome="'+t.nome+'">'+t.nome+'</option>'});
|
||||||
|
|
@ -129,15 +165,17 @@ async function init(){
|
||||||
}
|
}
|
||||||
data.campos.forEach(f=>{
|
data.campos.forEach(f=>{
|
||||||
html+='<div class="field"><label>'+f.label+(f.obrigatorio?' <span class="req">*</span>':'')+'</label>';
|
html+='<div class="field"><label>'+f.label+(f.obrigatorio?' <span class="req">*</span>':'')+'</label>';
|
||||||
|
const isTextarea = f.tipo === 'textarea' || (f.label && /observa/i.test(f.label));
|
||||||
if(f.tipo==='select'){
|
if(f.tipo==='select'){
|
||||||
html+='<select name="'+f.id+'"'+(f.obrigatorio?' required':'')+'><option value="">Selecione...</option>';
|
html+='<select name="'+f.id+'"'+(f.obrigatorio?' required':'')+'><option value="">Selecione...</option>';
|
||||||
(f.opcoes||[]).forEach(o=>{html+='<option value="'+o+'">'+o+'</option>'});
|
(f.opcoes||[]).forEach(o=>{html+='<option value="'+o+'">'+o+'</option>'});
|
||||||
html+='</select>';
|
html+='</select>';
|
||||||
}else if(f.tipo==='textarea'){
|
}else if(isTextarea){
|
||||||
html+='<textarea name="'+f.id+'"'+(f.obrigatorio?' required':'')+' placeholder="'+(f.placeholder||'')+'"></textarea>';
|
html+='<textarea name="'+f.id+'"'+(f.obrigatorio?' required':'')+' placeholder="'+(f.placeholder||'')+'"></textarea>';
|
||||||
}else{
|
}else{
|
||||||
let t=f.tipo;if(t==='phone'||t==='cpf')t='text';
|
let t=f.tipo;if(t==='phone'||t==='cpf'||t==='cep')t='text';
|
||||||
html+='<input type="'+t+'" name="'+f.id+'"'+(f.obrigatorio?' required':'')+' placeholder="'+(f.placeholder||'')+'">';
|
let dt=(f.tipo==='phone'||f.tipo==='cpf'||f.tipo==='cep')?(' data-type="'+f.tipo+'"'):'';
|
||||||
|
html+='<input type="'+t+'" name="'+f.id+'"'+(f.obrigatorio?' required':'')+dt+' placeholder="'+(f.placeholder||'')+'">';
|
||||||
}
|
}
|
||||||
html+='</div>';
|
html+='</div>';
|
||||||
});
|
});
|
||||||
|
|
@ -2898,7 +2936,9 @@ async function startServer() {
|
||||||
id: r.id, titulo: r.titulo, descricao: r.descricao, slug: r.slug,
|
id: r.id, titulo: r.titulo, descricao: r.descricao, slug: r.slug,
|
||||||
status: r.status, corPrimaria: r.cor_primaria, logoUrl: r.logo_url,
|
status: r.status, corPrimaria: r.cor_primaria, logoUrl: r.logo_url,
|
||||||
mensagemSucesso: r.mensagem_sucesso,
|
mensagemSucesso: r.mensagem_sucesso,
|
||||||
turmasPermitidas: r.turmas_permitidas || []
|
turmasPermitidas: r.turmas_permitidas || [],
|
||||||
|
exibirEmail: r.exibir_email !== false,
|
||||||
|
exibirTelefone: r.exibir_telefone !== false
|
||||||
}});
|
}});
|
||||||
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
||||||
});
|
});
|
||||||
|
|
@ -2908,8 +2948,13 @@ async function startServer() {
|
||||||
const c = req.body;
|
const c = req.body;
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`UPDATE prematricula_config SET titulo=$1, descricao=$2, slug=$3, status=$4,
|
`UPDATE prematricula_config SET titulo=$1, descricao=$2, slug=$3, status=$4,
|
||||||
cor_primaria=$5, logo_url=$6, mensagem_sucesso=$7, turmas_permitidas=$8, updated_at=NOW() WHERE id=1`,
|
cor_primaria=$5, logo_url=$6, mensagem_sucesso=$7, turmas_permitidas=$8,
|
||||||
[c.titulo, c.descricao, c.slug, c.status, c.corPrimaria || '#4f46e5', c.logoUrl || '', c.mensagemSucesso || '', c.turmasPermitidas || []]
|
exibir_email=$9, exibir_telefone=$10, updated_at=NOW() WHERE id=1`,
|
||||||
|
[
|
||||||
|
c.titulo, c.descricao, c.slug, c.status, c.corPrimaria || '#4f46e5',
|
||||||
|
c.logoUrl || '', c.mensagemSucesso || '', c.turmasPermitidas || [],
|
||||||
|
c.exibirEmail !== false, c.exibirTelefone !== false
|
||||||
|
]
|
||||||
);
|
);
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
} catch (e) { console.error(e); res.status(500).json({ error: e.message }); }
|
||||||
|
|
@ -3014,7 +3059,9 @@ async function startServer() {
|
||||||
res.json({
|
res.json({
|
||||||
config: {
|
config: {
|
||||||
titulo: cfg.titulo, descricao: cfg.descricao, corPrimaria: cfg.cor_primaria,
|
titulo: cfg.titulo, descricao: cfg.descricao, corPrimaria: cfg.cor_primaria,
|
||||||
logoUrl: cfg.logo_url, mensagemSucesso: cfg.mensagem_sucesso
|
logoUrl: cfg.logo_url, mensagemSucesso: cfg.mensagem_sucesso,
|
||||||
|
exibirEmail: cfg.exibir_email !== false,
|
||||||
|
exibirTelefone: cfg.exibir_telefone !== false
|
||||||
},
|
},
|
||||||
campos: camposRows.map(r => ({
|
campos: camposRows.map(r => ({
|
||||||
id: r.id, label: r.label, tipo: r.tipo, placeholder: r.placeholder,
|
id: r.id, label: r.label, tipo: r.tipo, placeholder: r.placeholder,
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,8 @@ export interface PreMatriculaConfig {
|
||||||
logoUrl: string;
|
logoUrl: string;
|
||||||
mensagemSucesso: string;
|
mensagemSucesso: string;
|
||||||
turmasPermitidas?: string[];
|
turmasPermitidas?: string[];
|
||||||
|
exibirEmail?: boolean;
|
||||||
|
exibirTelefone?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PreMatriculaInscricao {
|
export interface PreMatriculaInscricao {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue