From f10f213b9e92d27d876df3bf13b427c9e47c74f7 Mon Sep 17 00:00:00 2001 From: Sidney Date: Fri, 29 May 2026 19:43:21 -0300 Subject: [PATCH] feat: optimize pre-enrollment dynamic banners, field uploads and phone masks --- manager/components/PreMatricula.tsx | 1 + manager/scratch/cleanup_orphans.js | 21 ++++++++++++++++ manager/server.selfhosted.js | 27 +++++++++++++------- portal/server.selfhosted.js | 39 ++++++++++++++++++++++++++--- 4 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 manager/scratch/cleanup_orphans.js diff --git a/manager/components/PreMatricula.tsx b/manager/components/PreMatricula.tsx index 39f002d..08f6357 100644 --- a/manager/components/PreMatricula.tsx +++ b/manager/components/PreMatricula.tsx @@ -15,6 +15,7 @@ interface Props { const FIELD_TYPES: Record = { text: 'Texto', email: 'Email', phone: 'Telefone', cpf: 'CPF', date: 'Data', select: 'Seleção', textarea: 'Texto Longo', number: 'Número', + file: 'Imagem / Documento (Upload)', banner: 'Folder / Panfleto de Promoção (Banner)' }; diff --git a/manager/scratch/cleanup_orphans.js b/manager/scratch/cleanup_orphans.js new file mode 100644 index 0000000..05b8a59 --- /dev/null +++ b/manager/scratch/cleanup_orphans.js @@ -0,0 +1,21 @@ +import pg from 'pg'; + +const DATABASE_URL = 'postgresql://edumanager:EduManager2026!Seguro@150.230.87.131:5432/edumanager'; +const pool = new pg.Pool({ connectionString: DATABASE_URL }); + +async function cleanup() { + try { + const { rowCount } = await pool.query( + `DELETE FROM notas_boletim + WHERE prova_id IS NOT NULL AND prova_id NOT IN (SELECT id FROM provas)` + ); + console.log(`[Sucesso] Limpeza concluída: ${rowCount} registros órfãos de notas removidos!`); + } catch (error) { + console.error('[Erro] Falha ao limpar banco de dados:', error); + } finally { + await pool.end(); + process.exit(); + } +} + +cleanup(); diff --git a/manager/server.selfhosted.js b/manager/server.selfhosted.js index dac255c..17caa0b 100644 --- a/manager/server.selfhosted.js +++ b/manager/server.selfhosted.js @@ -79,7 +79,7 @@ function getPreMatriculaHTML(slug) { body{font-family:'Inter',sans-serif;background:#f8fafc;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px} .container{max-width:640px;width:100%;background:#fff;border-radius:24px;box-shadow:0 25px 50px -12px rgba(0,0,0,.1);overflow:hidden} .header{padding:40px 32px 24px;text-align:center} -.header img{width:96px;height:96px;border-radius:50%;margin:0 auto 24px;display:block;object-fit:cover;border:3px solid var(--primary,#4f46e5);box-shadow:0 10px 15px -3px rgba(0,0,0,.1)} +.header img{max-height:80px;max-width:240px;margin:0 auto 24px;display:block;object-fit:contain} .header h1{font-size:28px;font-weight:900;color:#0f172a;margin-bottom:8px} .header p{font-size:14px;color:#64748b;line-height:1.6} .form{padding:0 32px 32px} @@ -151,12 +151,21 @@ function maskCEP(v){ 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); + if(dt==='phone' || dt==='cpf' || dt==='cep'){ + const originalVal = e.target.value; + let newVal = originalVal; + if(dt==='phone') newVal = maskPhone(originalVal); + else if(dt==='cpf') newVal = maskCPF(originalVal); + else if(dt==='cep') newVal = maskCEP(originalVal); + + if (newVal !== originalVal) { + const start = e.target.selectionStart; + e.target.value = newVal; + try { + const diff = newVal.length - originalVal.length; + e.target.setSelectionRange(start + diff, start + diff); + } catch(err) {} + } } }); @@ -238,7 +247,7 @@ async function init(){ }else if(f.id==='_email'){ html+=''; }else if(f.id==='_telefone'){ - html+=''; + html+=''; }else if(f.id==='_turma'){ if(data.turmas.length>0){ html+=''; } } diff --git a/portal/server.selfhosted.js b/portal/server.selfhosted.js index 7a837a3..6706543 100644 --- a/portal/server.selfhosted.js +++ b/portal/server.selfhosted.js @@ -1050,15 +1050,40 @@ app.get('/manifest.json', async (req, res) => { const name = schoolData.profile?.name || 'EduManager'; const logo = normalizeStorageUrl(schoolData.logo) || '/vite.svg'; + // Detectar dinamicamente o tipo MIME com base na extensão do arquivo de logo (ex: webp, png, svg) + const ext = logo.split('.').pop().split('?')[0].toLowerCase(); + let mimeType = 'image/png'; + if (ext === 'jpg' || ext === 'jpeg') mimeType = 'image/jpeg'; + else if (ext === 'svg') mimeType = 'image/svg+xml'; + else if (ext === 'webp') mimeType = 'image/webp'; + res.json({ "short_name": name, "name": `${name} - Portal do Aluno`, "icons": [ { "src": logo, - "sizes": "192x192 512x512", - "type": "image/png", - "purpose": "any maskable" + "sizes": "192x192", + "type": mimeType, + "purpose": "any" + }, + { + "src": logo, + "sizes": "512x512", + "type": mimeType, + "purpose": "any" + }, + { + "src": logo, + "sizes": "192x192", + "type": mimeType, + "purpose": "maskable" + }, + { + "src": logo, + "sizes": "512x512", + "type": mimeType, + "purpose": "maskable" } ], "start_url": "/", @@ -1074,7 +1099,13 @@ app.get('/manifest.json', async (req, res) => { "icons": [ { "src": "/vite.svg", - "sizes": "192x192 512x512", + "sizes": "192x192", + "type": "image/svg+xml", + "purpose": "any" + }, + { + "src": "/vite.svg", + "sizes": "512x512", "type": "image/svg+xml", "purpose": "any" }