Adicionado CRUD Minha Turma e Observações Especiais

This commit is contained in:
2026-06-06 15:27:54 +00:00
parent 3781c848da
commit a516989016
2 changed files with 427 additions and 2 deletions
+302 -1
View File
@@ -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 = '<div class="obs-empty">Carregando...</div>';
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 = '<div class="obs-empty">Erro ao carregar observações.</div>';
}
});
}
// 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 = '<div class="obs-empty">Carregando...</div>';
@@ -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 = '<div class="obs-empty">Nenhuma criança com "Educação Especial" cadastrada na Minha Turma.</div>';
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 = '<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Nenhuma criança cadastrada.</div>';
return;
}
// Sort by name
turma.sort((a, b) => (a.nome || '').localeCompare(b.nome || ''));
childrenList.innerHTML = turma.map(c => `
<div style="background: var(--bg-secondary); border: 1px solid var(--border-light); padding: 12px; border-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-weight: 600; color: var(--text-primary);">${escapeHtml(c.nome)} ${c.especial ? '⭐' : ''}</div>
<div style="font-size: 0.8rem; color: var(--text-secondary);">${escapeHtml(c.turma || '')} - ${escapeHtml(c.periodo || '')}</div>
</div>
<div style="display: flex; gap: 8px;">
<button onclick="editChild('${c.id}')" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px;" title="Editar"></button>
<button onclick="deleteChild('${c.id}')" style="background: none; border: none; color: #ef4444; cursor: pointer; padding: 4px;" title="Excluir">🗑</button>
</div>
</div>
`).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');