diff --git a/public/app.js b/public/app.js index 5b6a7be..9fc369d 100644 --- a/public/app.js +++ b/public/app.js @@ -6785,5 +6785,410 @@ document.addEventListener('DOMContentLoaded', () => { btnCloseMindLabHistory.addEventListener('click', () => { mindLabHistoryModal.style.display = 'none'; }); + }); + } + + // ============================================================ + // CRIAR CARTAZES + // ============================================================ + const barBtnCartazes = document.getElementById('barBtnCartazes'); + const cartazesModal = document.getElementById('cartazesModal'); + const btnCloseCartazesModal = document.getElementById('btnCloseCartazesModal'); + + const tabCartazesCriar = document.getElementById('tabCartazesCriar'); + const tabCartazesHistorico = document.getElementById('tabCartazesHistorico'); + const panelCartazesCriar = document.getElementById('panelCartazesCriar'); + const panelCartazesHistorico = document.getElementById('panelCartazesHistorico'); + + const cartazTituloInput = document.getElementById('cartazTituloInput'); + const cartazTemaInput = document.getElementById('cartazTemaInput'); + const cartazLayoutSelect = document.getElementById('cartazLayoutSelect'); + const cartazBgColor = document.getElementById('cartazBgColor'); + const cartazTextColor = document.getElementById('cartazTextColor'); + const cartazFontSelect = document.getElementById('cartazFontSelect'); + const cartazBorderSelect = document.getElementById('cartazBorderSelect'); + const cartazPromptImagem = document.getElementById('cartazPromptImagem'); + const btnGenerateCartaz = document.getElementById('btnGenerateCartaz'); + const cartazGenerateLoader = document.getElementById('cartazGenerateLoader'); + + const btnEditCartazToggle = document.getElementById('btnEditCartazToggle'); + const btnPrintCartaz = document.getElementById('btnPrintCartaz'); + const cartazEditArea = document.getElementById('cartazEditArea'); + const cartazHtmlEditor = document.getElementById('cartazHtmlEditor'); + const btnUpdateCartazPreview = document.getElementById('btnUpdateCartazPreview'); + + const cartazPrintableArea = document.getElementById('cartazPrintableArea'); + const cartazPreviewTitle = document.getElementById('cartazPreviewTitle'); + const cartazPreviewContent = document.getElementById('cartazPreviewContent'); + const cartazPreviewImageContainer = document.getElementById('cartazPreviewImageContainer'); + const cartazPreviewImage = document.getElementById('cartazPreviewImage'); + + const cartazesHistoryContainer = document.getElementById('cartazesHistoryContainer'); + const cartazesNoData = document.getElementById('cartazesNoData'); + + let activeCartazData = null; + + // Toggle Modal + if (barBtnCartazes) { + barBtnCartazes.addEventListener('click', () => { + cartazesModal.style.display = 'flex'; + switchCartazTab('criar'); + }); + } + + if (btnCloseCartazesModal) { + btnCloseCartazesModal.addEventListener('click', () => { + cartazesModal.style.display = 'none'; + }); + } + + cartazesModal.addEventListener('click', (e) => { + if (e.target === cartazesModal) cartazesModal.style.display = 'none'; + }); + + // Switch Tab + function switchCartazTab(tab) { + if (tab === 'criar') { + tabCartazesCriar.classList.add('active'); + tabCartazesHistorico.classList.remove('active'); + panelCartazesCriar.style.display = 'flex'; + panelCartazesHistorico.style.display = 'none'; + + tabCartazesCriar.style.color = 'var(--text-primary)'; + tabCartazesHistorico.style.color = 'var(--text-secondary)'; + } else { + tabCartazesCriar.classList.remove('active'); + tabCartazesHistorico.classList.add('active'); + panelCartazesCriar.style.display = 'none'; + panelCartazesHistorico.style.display = 'flex'; + + tabCartazesCriar.style.color = 'var(--text-secondary)'; + tabCartazesHistorico.style.color = 'var(--text-primary)'; + loadCartazesHistory(); + } + } + + if (tabCartazesCriar) tabCartazesCriar.addEventListener('click', () => switchCartazTab('criar')); + if (tabCartazesHistorico) tabCartazesHistorico.addEventListener('click', () => switchCartazTab('historico')); + + // Live Style Updates + function updateLivePreview() { + const bg = cartazBgColor.value; + const text = cartazTextColor.value; + const font = cartazFontSelect.value; + const border = cartazBorderSelect.value; + const title = cartazTituloInput.value.trim() || 'Título do Cartaz'; + + cartazPrintableArea.style.backgroundColor = bg; + cartazPrintableArea.style.color = text; + cartazPrintableArea.style.fontFamily = font; + cartazPreviewTitle.textContent = title; + cartazPreviewTitle.style.borderColor = text; + + if (border === 'none') { + cartazPrintableArea.style.border = 'none'; + } else { + cartazPrintableArea.style.border = `${border} ${text}`; + } + } + + [cartazBgColor, cartazTextColor, cartazFontSelect, cartazBorderSelect, cartazTituloInput].forEach(elem => { + if (elem) elem.addEventListener('input', updateLivePreview); + }); + + // Preset Palettes + document.querySelectorAll('.btn-palette-preset').forEach(btn => { + btn.addEventListener('click', (e) => { + const bg = e.target.dataset.bg; + const text = e.target.dataset.text; + cartazBgColor.value = bg; + cartazTextColor.value = text; + updateLivePreview(); + }); + }); + + // Toggle HTML Editor + if (btnEditCartazToggle) { + btnEditCartazToggle.addEventListener('click', () => { + if (cartazEditArea.style.display === 'none') { + cartazEditArea.style.display = 'flex'; + btnEditCartazToggle.textContent = '👁️ Ocultar Editor'; + } else { + cartazEditArea.style.display = 'none'; + btnEditCartazToggle.textContent = '✏️ Editar Conteúdo'; + } + }); + } + + // Update from Editor + if (btnUpdateCartazPreview) { + btnUpdateCartazPreview.addEventListener('click', () => { + cartazPreviewContent.innerHTML = cartazHtmlEditor.value; + }); + } + + // Generate Poster + if (btnGenerateCartaz) { + btnGenerateCartaz.addEventListener('click', async () => { + const titulo = cartazTituloInput.value.trim(); + const tema = cartazTemaInput.value.trim(); + const layout = cartazLayoutSelect.value; + const promptImagem = cartazPromptImagem.value.trim(); + + if (!titulo || !tema) { + showToast('Por favor, defina o Título e o Tema do Cartaz.', 'error'); + return; + } + + btnGenerateCartaz.disabled = true; + cartazGenerateLoader.style.display = 'flex'; + + try { + const res = await fetch('/api/cartazes/generate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('token')}` + }, + body: JSON.stringify({ + titulo, + tema, + corFundo: cartazBgColor.value, + corTexto: cartazTextColor.value, + layout, + promptImagem + }) + }); + + const data = await res.json(); + if (res.ok) { + activeCartazData = data; + + // Preencher Preview + cartazPreviewContent.innerHTML = data.conteudo; + cartazHtmlEditor.value = data.conteudo; + + if (data.imagemUrl) { + cartazPreviewImage.src = data.imagemUrl; + cartazPreviewImageContainer.style.display = 'block'; + } else { + cartazPreviewImageContainer.style.display = 'none'; + } + + updateLivePreview(); + showToast('Cartaz criado e salvo com sucesso!', 'success'); + } else { + showToast(data.error || 'Erro ao gerar cartaz.', 'error'); + } + } catch (err) { + console.error(err); + showToast('Erro ao se conectar com o servidor.', 'error'); + } finally { + btnGenerateCartaz.disabled = false; + cartazGenerateLoader.style.display = 'none'; + } + }); + } + + // Print Poster + if (btnPrintCartaz) { + btnPrintCartaz.addEventListener('click', () => { + const title = cartazTituloInput.value.trim() || 'Cartaz Pedagógico'; + const bg = cartazBgColor.value; + const text = cartazTextColor.value; + const font = cartazFontSelect.value; + const border = cartazBorderSelect.value; + const imgContainerHtml = cartazPreviewImageContainer.style.display === 'block' + ? `
` + : ''; + + const printWindow = window.open('', '_blank'); + printWindow.document.write(` + + +,
Tema: ${tema}
Não foi possível gerar com IA. Edite o conteúdo livremente.
`; + } + + // Clean HTML from backticks + conteudoHtml = conteudoHtml.replace(/```html|```/g, '').trim(); + + // 2. Gerar Imagem Ilustrativa se solicitado + let generatedImageUrl = null; + if (promptImagem && promptImagem.trim().length > 3) { + try { + console.log('[Cartazes] Gerando ilustração para o cartaz...'); + const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, ''); + const genResp = await fetch(`${minmBase}/v1/image_generation`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: 'image-01', + prompt: `${promptImagem}. Estilo ilustração de livro infantil de alta qualidade, 3D Pixar, cores suaves, traço limpo, sem texto.`, + n: 1 + }) + }); + + if (genResp.ok) { + const genData = await genResp.json(); + const rawImgUrl = genData.data?.image_urls?.[0]; + if (rawImgUrl) { + const mediaDir = path.join(__dirname, 'public', 'generated-media'); + if (!fs.existsSync(mediaDir)) fs.mkdirSync(mediaDir, { recursive: true }); + + const imgFetch = await fetch(rawImgUrl); + if (imgFetch.ok) { + const imgBuffer = Buffer.from(await imgFetch.arrayBuffer()); + const fileName = `cartaz_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`; + fs.writeFileSync(path.join(mediaDir, fileName), imgBuffer); + generatedImageUrl = `/generated-media/${fileName}`; + } + } + } + } catch (imgErr) { + console.error('[Cartazes] Erro na geração de imagem:', imgErr); + } + } + + // 3. Salvar no Banco + const dbRes = await dbPool.query( + `INSERT INTO escola.cartazes (usuario_id, titulo, tema, cor_fundo, cor_texto, layout, conteudo, imagem_url) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at`, + [usuarioId, titulo, tema, corFundo || '#ffffff', corTexto || '#1e293b', layout, conteudoHtml, generatedImageUrl] + ); + + res.json({ + success: true, + id: dbRes.rows[0].id, + titulo, + tema, + corFundo, + corTexto, + layout, + conteudo: conteudoHtml, + imagemUrl: generatedImageUrl, + createdAt: dbRes.rows[0].created_at + }); + + } catch (err) { + console.error('Erro ao criar cartaz:', err); + res.status(500).json({ error: 'Erro ao criar cartaz: ' + err.message }); + } +}); + +app.get('/api/cartazes/list', requireAuth, async (req, res) => { + const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000'; + try { + const dbRes = await dbPool.query( + `SELECT id, titulo, tema, cor_fundo as "corFundo", cor_texto as "corTexto", layout, imagem_url as "imagemUrl", created_at as "createdAt" + FROM escola.cartazes WHERE usuario_id = $1 ORDER BY created_at DESC`, + [usuarioId] + ); + res.json(dbRes.rows); + } catch (err) { + console.error('Erro ao listar cartazes:', err); + res.status(500).json({ error: 'Erro ao listar cartazes.' }); + } +}); + +app.get('/api/cartazes/:id', requireAuth, async (req, res) => { + const { id } = req.params; + const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000'; + try { + const dbRes = await dbPool.query( + `SELECT id, titulo, tema, cor_fundo as "corFundo", cor_texto as "corTexto", layout, conteudo, imagem_url as "imagemUrl", created_at as "createdAt" + FROM escola.cartazes WHERE id = $1 AND usuario_id = $2`, + [id, usuarioId] + ); + if (dbRes.rows.length === 0) { + return res.status(404).json({ error: 'Cartaz não encontrado.' }); + } + res.json(dbRes.rows[0]); + } catch (err) { + console.error('Erro ao buscar cartaz:', err); + res.status(500).json({ error: 'Erro ao buscar cartaz.' }); + } +}); + +app.delete('/api/cartazes/:id', requireAuth, async (req, res) => { + const { id } = req.params; + const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000'; + try { + const dbRes = await dbPool.query( + `DELETE FROM escola.cartazes WHERE id = $1 AND usuario_id = $2`, + [id, usuarioId] + ); + if (dbRes.rowCount === 0) { + return res.status(404).json({ error: 'Cartaz não encontrado ou não pertence ao usuário.' }); + } + res.json({ success: true, message: 'Cartaz excluído com sucesso.' }); + } catch (err) { + console.error('Erro ao deletar cartaz:', err); + res.status(500).json({ error: 'Erro ao deletar cartaz.' }); + } +}); + // ============================================================ // ROTAS DO MUSICANDO IDEIAS // ============================================================ @@ -1848,6 +2049,20 @@ async function initDatabase() { historia TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); + + -- Cartazes (Criar Cartazes) + CREATE TABLE IF NOT EXISTS escola.cartazes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', + titulo VARCHAR(255) NOT NULL, + tema VARCHAR(255) NOT NULL, + cor_fundo VARCHAR(50) NOT NULL, + cor_texto VARCHAR(50) NOT NULL, + layout VARCHAR(50) NOT NULL, -- 'mural', 'regras', 'calendario', 'livre' + conteudo TEXT NOT NULL, + imagem_url TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + ); `); // Garantir que a coluna character_description_english exista na tabela projetos_comics