diff --git a/public/app.js b/public/app.js index 7fc71d0..a034da2 100644 --- a/public/app.js +++ b/public/app.js @@ -2778,17 +2778,43 @@ const initApp = () => { // ============================================================ const obsModal = document.getElementById('obsModal'); const btnObservacoes = document.getElementById('btnObservacoes'); + const btnObsEspeciais = document.getElementById('btnObsEspeciais'); const btnCloseObsModal = document.getElementById('btnCloseObsModal'); + const obsModalTitle = document.getElementById('obsModalTitle'); const obsList = document.getElementById('obsList'); const obsFilterMes = document.getElementById('obsFilterMes'); const obsFilterTag = document.getElementById('obsFilterTag'); const obsSearchCrianca = document.getElementById('obsSearchCrianca'); let allObservations = []; + let isSpecialObsMode = false; // Abrir modal de observações if (btnObservacoes) { btnObservacoes.addEventListener('click', async () => { + isSpecialObsMode = false; + if (obsModalTitle) obsModalTitle.textContent = '📒 Minhas Observações'; + obsModal.style.display = 'flex'; + obsList.innerHTML = '
Carregando...
'; + + try { + const res = await fetch('/api/observacoes'); + const data = await res.json(); + allObservations = data.observations || []; + populateMonthFilter(); + renderObsList(); + } catch (err) { + console.error('Erro ao carregar observações:', err); + obsList.innerHTML = '
Erro ao carregar observações.
'; + } + }); + } + + // Abrir modal de observações Especiais + if (btnObsEspeciais) { + btnObsEspeciais.addEventListener('click', async () => { + isSpecialObsMode = true; + if (obsModalTitle) obsModalTitle.textContent = '📒 Observações Educação Especial'; obsModal.style.display = 'flex'; obsList.innerHTML = '
Carregando...
'; @@ -2838,11 +2864,30 @@ const initApp = () => { const tag = obsFilterTag?.value || ''; const search = obsSearchCrianca?.value.toLowerCase() || ''; + let especialKidsNames = []; + if (isSpecialObsMode) { + const turma = JSON.parse(localStorage.getItem('camila_turma') || '[]'); + especialKidsNames = turma.filter(c => c.especial).map(c => (c.nome || '').toLowerCase().trim()).filter(Boolean); + if (especialKidsNames.length === 0) { + obsList.innerHTML = '
Nenhuma criança com "Educação Especial" cadastrada na Minha Turma.
'; + return; + } + } + const filtered = allObservations.filter(o => { if (mes && !o.date?.startsWith(mes)) return false; if (tag && !o.tags?.includes(tag)) return false; + + const nomes = (o.criancas || []).join(' ').toLowerCase(); + + if (isSpecialObsMode) { + // Must contain at least one special kid's name + const preview = (o.preview || '').toLowerCase(); + const isEspecial = especialKidsNames.some(ekn => nomes.includes(ekn) || preview.includes(ekn)); + if (!isEspecial) return false; + } + if (search) { - const nomes = (o.criancas || []).join(' ').toLowerCase(); if (!nomes.includes(search) && !(o.preview || '').toLowerCase().includes(search)) return false; } return true; @@ -2886,6 +2931,262 @@ const initApp = () => { }); }; + // ============================================================ + // MODAL: MINHA TURMA + // ============================================================ + const minhaTurmaModal = document.getElementById('minhaTurmaModal'); + const btnMinhaTurma = document.getElementById('btnMinhaTurma'); + const btnCloseMinhaTurmaModal = document.getElementById('btnCloseMinhaTurmaModal'); + const childrenList = document.getElementById('childrenList'); + const btnCreateChild = document.getElementById('btnCreateChild'); + const btnCancelChild = document.getElementById('btnCancelChild'); + const btnSaveChild = document.getElementById('btnSaveChild'); + const childFormTitle = document.getElementById('childFormTitle'); + + // Fields + const cFormId = document.getElementById('childFormId'); + const cFormTurma = document.getElementById('childFormTurma'); + const cFormSala = document.getElementById('childFormSala'); + const cFormPeriodo = document.getElementById('childFormPeriodo'); + const cFormAuxiliares = document.getElementById('childFormAuxiliares'); + const cFormNome = document.getElementById('childFormNome'); + const cFormApelido = document.getElementById('childFormApelido'); + const cFormDataNasc = document.getElementById('childFormDataNasc'); + const cFormEspecial = document.getElementById('childFormEspecial'); + const divEspecialDetalhes = document.getElementById('divEspecialDetalhes'); + const cFormEspecialDetalhes = document.getElementById('childFormEspecialDetalhes'); + const cFormPais = document.getElementById('childFormPais'); + const cFormAutorizados = document.getElementById('childFormAutorizados'); + + // Toggle Especial Fields + if (cFormEspecial) { + cFormEspecial.addEventListener('change', () => { + if (cFormEspecial.checked) { + divEspecialDetalhes.style.display = 'flex'; + } else { + divEspecialDetalhes.style.display = 'none'; + } + }); + } + + const loadTurma = () => { + return JSON.parse(localStorage.getItem('camila_turma') || '[]'); + }; + + const saveTurma = (turma) => { + localStorage.setItem('camila_turma', JSON.stringify(turma)); + }; + + const renderTurmaList = () => { + if (!childrenList) return; + const turma = loadTurma(); + if (turma.length === 0) { + childrenList.innerHTML = '
Nenhuma criança cadastrada.
'; + return; + } + + // Sort by name + turma.sort((a, b) => (a.nome || '').localeCompare(b.nome || '')); + + childrenList.innerHTML = turma.map(c => ` +
+
+
${escapeHtml(c.nome)} ${c.especial ? '⭐' : ''}
+
${escapeHtml(c.turma || '')} - ${escapeHtml(c.periodo || '')}
+
+
+ + +
+
+ `).join(''); + }; + + const clearChildForm = () => { + cFormId.value = ''; + cFormTurma.value = ''; + cFormSala.value = ''; + cFormPeriodo.value = 'Integral'; + cFormAuxiliares.value = ''; + cFormNome.value = ''; + cFormApelido.value = ''; + cFormDataNasc.value = ''; + cFormEspecial.checked = false; + cFormEspecialDetalhes.value = ''; + divEspecialDetalhes.style.display = 'none'; + cFormPais.value = ''; + cFormAutorizados.value = ''; + childFormTitle.textContent = 'Cadastrar Criança'; + }; + + window.editChild = (id) => { + const turma = loadTurma(); + const c = turma.find(x => x.id === id); + if (!c) return; + + cFormId.value = c.id; + cFormTurma.value = c.turma || ''; + cFormSala.value = c.sala || ''; + cFormPeriodo.value = c.periodo || 'Integral'; + cFormAuxiliares.value = c.auxiliares || ''; + cFormNome.value = c.nome || ''; + cFormApelido.value = c.apelido || ''; + cFormDataNasc.value = c.dataNasc || ''; + cFormEspecial.checked = !!c.especial; + cFormEspecialDetalhes.value = c.especialDetalhes || ''; + divEspecialDetalhes.style.display = c.especial ? 'flex' : 'none'; + cFormPais.value = c.pais || ''; + cFormAutorizados.value = c.autorizados || ''; + + childFormTitle.textContent = 'Editar Criança'; + }; + + window.deleteChild = async (id) => { + const isOk = await showCustomConfirm('Excluir', 'Deseja excluir esta criança? Isso não removerá as observações já feitas, apenas removerá da lista da turma.', true); + if (isOk) { + let turma = loadTurma(); + turma = turma.filter(x => x.id !== id); + saveTurma(turma); + renderTurmaList(); + clearChildForm(); + } + }; + + if (btnMinhaTurma) { + btnMinhaTurma.addEventListener('click', () => { + minhaTurmaModal.style.display = 'flex'; + renderTurmaList(); + clearChildForm(); + }); + } + + if (btnCloseMinhaTurmaModal) btnCloseMinhaTurmaModal.addEventListener('click', () => minhaTurmaModal.style.display = 'none'); + if (minhaTurmaModal) { + minhaTurmaModal.addEventListener('click', (e) => { + if (e.target === minhaTurmaModal) minhaTurmaModal.style.display = 'none'; + }); + } + + if (btnCreateChild) btnCreateChild.addEventListener('click', clearChildForm); + if (btnCancelChild) btnCancelChild.addEventListener('click', clearChildForm); + + if (btnSaveChild) { + btnSaveChild.addEventListener('click', () => { + if (!cFormNome.value.trim()) { + showCustomAlert('Aviso', 'O Nome Completo é obrigatório.'); + return; + } + + let turma = loadTurma(); + const id = cFormId.value || 'child_' + Date.now(); + + const newChild = { + id, + turma: cFormTurma.value.trim(), + sala: cFormSala.value.trim(), + periodo: cFormPeriodo.value, + auxiliares: cFormAuxiliares.value.trim(), + nome: cFormNome.value.trim(), + apelido: cFormApelido.value.trim(), + dataNasc: cFormDataNasc.value, + especial: cFormEspecial.checked, + especialDetalhes: cFormEspecialDetalhes.value.trim(), + pais: cFormPais.value.trim(), + autorizados: cFormAutorizados.value.trim() + }; + + const idx = turma.findIndex(x => x.id === id); + if (idx > -1) { + turma[idx] = newChild; + } else { + turma.push(newChild); + } + + saveTurma(turma); + renderTurmaList(); + clearChildForm(); + }); + } + + // Preencher com Voz + const btnVoiceRecordChild = document.getElementById('btnVoiceRecordChild'); + if (btnVoiceRecordChild && window.SpeechRecognition || window.webkitSpeechRecognition) { + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; + const formRecognition = new SpeechRecognition(); + formRecognition.lang = 'pt-BR'; + formRecognition.continuous = false; + formRecognition.interimResults = false; + + formRecognition.onstart = () => { + btnVoiceRecordChild.innerHTML = '🛑 Gravando... (Clique para parar)'; + btnVoiceRecordChild.style.background = 'rgba(239, 68, 68, 0.1)'; + btnVoiceRecordChild.style.color = '#ef4444'; + btnVoiceRecordChild.style.borderColor = '#ef4444'; + }; + + formRecognition.onresult = async (event) => { + const transcript = event.results[0][0].transcript; + btnVoiceRecordChild.innerHTML = '⏳ Processando com IA...'; + + try { + const response = await fetch('/api/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + messages: [{ + role: 'user', + content: `A professora ditou as seguintes informações para cadastrar uma criança na turma: "${transcript}". +Por favor, extraia os dados e retorne APENAS um JSON válido com os seguintes campos: +"turma", "sala", "periodo", "auxiliares", "nome", "apelido", "dataNasc" (formato YYYY-MM-DD), "especial" (boolean), "especialDetalhes", "pais", "autorizados". +Se algum dado não for mencionado, deixe a string vazia ou false para boolean.` + }], + temperature: 0.1 + }) + }); + + if (response.ok) { + const data = await response.json(); + const assistantReply = data.message; + // Extract JSON block + const match = assistantReply.match(/```json\n([\s\S]*?)\n```/) || assistantReply.match(/\{[\s\S]*\}/); + if (match) { + const parsed = JSON.parse(match[1] || match[0]); + if (parsed.turma) cFormTurma.value = parsed.turma; + if (parsed.sala) cFormSala.value = parsed.sala; + if (parsed.periodo) cFormPeriodo.value = parsed.periodo; + if (parsed.auxiliares) cFormAuxiliares.value = parsed.auxiliares; + if (parsed.nome) cFormNome.value = parsed.nome; + if (parsed.apelido) cFormApelido.value = parsed.apelido; + if (parsed.dataNasc) cFormDataNasc.value = parsed.dataNasc; + if (parsed.especial) cFormEspecial.checked = parsed.especial; + divEspecialDetalhes.style.display = cFormEspecial.checked ? 'flex' : 'none'; + if (parsed.especialDetalhes) cFormEspecialDetalhes.value = parsed.especialDetalhes; + if (parsed.pais) cFormPais.value = parsed.pais; + if (parsed.autorizados) cFormAutorizados.value = parsed.autorizados; + } + } + } catch (err) { + console.error('Erro ao processar voz:', err); + showCustomAlert('Erro', 'Não foi possível extrair os dados. Tente novamente ou preencha manualmente.'); + } + }; + + formRecognition.onend = () => { + btnVoiceRecordChild.innerHTML = '🎤 Preencher com Voz'; + btnVoiceRecordChild.style.background = 'rgba(16, 163, 127, 0.1)'; + btnVoiceRecordChild.style.color = 'var(--brand-green)'; + btnVoiceRecordChild.style.borderColor = 'var(--brand-green)'; + }; + + btnVoiceRecordChild.addEventListener('click', () => { + if (btnVoiceRecordChild.innerHTML.includes('Gravando')) { + formRecognition.stop(); + } else { + formRecognition.start(); + } + }); + } + // Mostra detalhe da observação em modal const showObsDetail = (mdText) => { const modal = document.createElement('div'); diff --git a/public/index.html b/public/index.html index 1a9809f..1ea1251 100644 --- a/public/index.html +++ b/public/index.html @@ -82,6 +82,26 @@ Minhas Observações + + + + + + + + + @@ -461,7 +481,7 @@