Implementação do banco de dados das Turmas e Alunos no Supabase e Refatoração do Modal de Cadastro
This commit is contained in:
+482
@@ -0,0 +1,482 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const appFile = path.join(__dirname, 'public', 'app.js');
|
||||||
|
let appCode = fs.readFileSync(appFile, 'utf8');
|
||||||
|
|
||||||
|
const startIndex = appCode.indexOf('// ============================================================\n // MODAL: MINHA TURMA');
|
||||||
|
const endIndex = appCode.indexOf('// ============================================================\n // CRUD DE MODELOS DE RELATÓRIO');
|
||||||
|
|
||||||
|
if (startIndex === -1 || endIndex === -1) {
|
||||||
|
console.error("Could not find start or end index for replacement");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newLogic = `// ============================================================
|
||||||
|
// MODAL: MINHA TURMA (COM MÚLTIPLAS TURMAS E SUPABASE)
|
||||||
|
// ============================================================
|
||||||
|
const minhaTurmaModal = document.getElementById('minhaTurmaModal');
|
||||||
|
const btnMinhaTurma = document.getElementById('btnMinhaTurma');
|
||||||
|
const btnCloseMinhaTurmaModal = document.getElementById('btnCloseMinhaTurmaModal');
|
||||||
|
|
||||||
|
// Turmas List & Form
|
||||||
|
const turmasList = document.getElementById('turmasList');
|
||||||
|
const btnCreateTurma = document.getElementById('btnCreateTurma');
|
||||||
|
const btnCancelTurma = document.getElementById('btnCancelTurma');
|
||||||
|
const btnSaveTurmaConfig = document.getElementById('btnSaveTurmaConfig');
|
||||||
|
const configTurmaId = document.getElementById('configTurmaId');
|
||||||
|
const configTurmaNome = document.getElementById('configTurmaNome');
|
||||||
|
const configTurmaSala = document.getElementById('configTurmaSala');
|
||||||
|
const configTurmaPeriodo = document.getElementById('configTurmaPeriodo');
|
||||||
|
const configTurmaProfTitular = document.getElementById('configTurmaProfTitular');
|
||||||
|
const configTurmaProfAux1 = document.getElementById('configTurmaProfAux1');
|
||||||
|
const configTurmaProfAux2 = document.getElementById('configTurmaProfAux2');
|
||||||
|
const configTurmaCuidadora = document.getElementById('configTurmaCuidadora');
|
||||||
|
const turmaFormTitle = document.getElementById('turmaFormTitle');
|
||||||
|
|
||||||
|
// Alunos List & Form
|
||||||
|
const filterAlunosTurma = document.getElementById('filterAlunosTurma');
|
||||||
|
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');
|
||||||
|
const cFormId = document.getElementById('childFormId');
|
||||||
|
const cFormTurma = document.getElementById('childFormTurma');
|
||||||
|
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');
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
const tabTurmaConfig = document.getElementById('tabTurmaConfig');
|
||||||
|
const tabTurmaAlunos = document.getElementById('tabTurmaAlunos');
|
||||||
|
const turmaConfigSection = document.getElementById('turmaConfigSection');
|
||||||
|
const turmaAlunosSection = document.getElementById('turmaAlunosSection');
|
||||||
|
|
||||||
|
let currentTurmas = [];
|
||||||
|
let currentAlunos = [];
|
||||||
|
|
||||||
|
// API Calls
|
||||||
|
const fetchTurmas = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/turmas');
|
||||||
|
currentTurmas = await res.json();
|
||||||
|
populateTurmaSelects();
|
||||||
|
return currentTurmas;
|
||||||
|
} catch(e) { console.error(e); return []; }
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchAlunos = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/alunos');
|
||||||
|
currentAlunos = await res.json();
|
||||||
|
localStorage.setItem('camila_turma', JSON.stringify(currentAlunos));
|
||||||
|
return currentAlunos;
|
||||||
|
} catch(e) { console.error(e); return []; }
|
||||||
|
};
|
||||||
|
|
||||||
|
const populateTurmaSelects = () => {
|
||||||
|
const opts = '<option value="">Todas as Turmas</option>' + currentTurmas.map(t => \`<option value="\${t.id}">\${t.nome}</option>\`).join('');
|
||||||
|
if(filterAlunosTurma) filterAlunosTurma.innerHTML = opts;
|
||||||
|
const formOpts = '<option value="">Selecione uma turma...</option>' + currentTurmas.map(t => \`<option value="\${t.id}">\${t.nome}</option>\`).join('');
|
||||||
|
if(cFormTurma) cFormTurma.innerHTML = formOpts;
|
||||||
|
if(document.getElementById('emitirFilterTurma')) {
|
||||||
|
document.getElementById('emitirFilterTurma').innerHTML = '<option value="">Todas as turmas</option>' + currentTurmas.map(t => \`<option value="\${t.id}">\${t.nome}</option>\`).join('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tabs Logic
|
||||||
|
if (tabTurmaConfig) {
|
||||||
|
tabTurmaConfig.addEventListener('click', () => {
|
||||||
|
tabTurmaConfig.classList.add('active');
|
||||||
|
tabTurmaConfig.style.borderBottomColor = 'var(--brand-green)';
|
||||||
|
tabTurmaConfig.style.color = 'var(--brand-green)';
|
||||||
|
tabTurmaAlunos.classList.remove('active');
|
||||||
|
tabTurmaAlunos.style.borderBottomColor = 'transparent';
|
||||||
|
tabTurmaAlunos.style.color = 'var(--text-secondary)';
|
||||||
|
turmaConfigSection.style.display = 'flex';
|
||||||
|
turmaAlunosSection.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (tabTurmaAlunos) {
|
||||||
|
tabTurmaAlunos.addEventListener('click', () => {
|
||||||
|
tabTurmaAlunos.classList.add('active');
|
||||||
|
tabTurmaAlunos.style.borderBottomColor = 'var(--brand-green)';
|
||||||
|
tabTurmaAlunos.style.color = 'var(--brand-green)';
|
||||||
|
tabTurmaConfig.classList.remove('active');
|
||||||
|
tabTurmaConfig.style.borderBottomColor = 'transparent';
|
||||||
|
tabTurmaConfig.style.color = 'var(--text-secondary)';
|
||||||
|
turmaAlunosSection.style.display = 'flex';
|
||||||
|
turmaConfigSection.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turmas Logic
|
||||||
|
const clearTurmaForm = () => {
|
||||||
|
configTurmaId.value = '';
|
||||||
|
configTurmaNome.value = '';
|
||||||
|
configTurmaSala.value = '';
|
||||||
|
configTurmaPeriodo.value = 'Integral';
|
||||||
|
configTurmaProfTitular.value = '';
|
||||||
|
configTurmaProfAux1.value = '';
|
||||||
|
configTurmaProfAux2.value = '';
|
||||||
|
configTurmaCuidadora.value = '';
|
||||||
|
turmaFormTitle.textContent = 'Cadastrar Turma';
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTurmasList = () => {
|
||||||
|
if(!turmasList) return;
|
||||||
|
if(currentTurmas.length === 0) {
|
||||||
|
turmasList.innerHTML = '<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Nenhuma turma cadastrada.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
turmasList.innerHTML = currentTurmas.map(t => \`
|
||||||
|
<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(t.nome)}</div>
|
||||||
|
<div style="font-size: 0.8rem; color: var(--text-secondary);">\${t.periodo || ''} \${t.sala ? '- ' + t.sala : ''}</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px;">
|
||||||
|
<button onclick="editTurma('\${t.id}')" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px;" title="Editar">✏️</button>
|
||||||
|
<button onclick="deleteTurma('\${t.id}')" style="background: none; border: none; color: #ef4444; cursor: pointer; padding: 4px;" title="Excluir">🗑️</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`).join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.editTurma = (id) => {
|
||||||
|
const t = currentTurmas.find(x => x.id === id);
|
||||||
|
if(!t) return;
|
||||||
|
configTurmaId.value = t.id;
|
||||||
|
configTurmaNome.value = t.nome || '';
|
||||||
|
configTurmaSala.value = t.sala || '';
|
||||||
|
configTurmaPeriodo.value = t.periodo || 'Integral';
|
||||||
|
configTurmaProfTitular.value = t.prof_titular || '';
|
||||||
|
configTurmaProfAux1.value = t.prof_aux1 || '';
|
||||||
|
configTurmaProfAux2.value = t.prof_aux2 || '';
|
||||||
|
configTurmaCuidadora.value = t.cuidadora || '';
|
||||||
|
turmaFormTitle.textContent = 'Editar Turma';
|
||||||
|
};
|
||||||
|
|
||||||
|
window.deleteTurma = async (id) => {
|
||||||
|
const isOk = await showCustomConfirm('Excluir Turma', 'Deseja excluir esta turma? Todos os alunos associados a ela também serão excluídos.', true);
|
||||||
|
if(isOk) {
|
||||||
|
await fetch('/api/turmas/' + id, { method: 'DELETE' });
|
||||||
|
await fetchTurmas();
|
||||||
|
await fetchAlunos();
|
||||||
|
renderTurmasList();
|
||||||
|
renderAlunosList();
|
||||||
|
clearTurmaForm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(btnCreateTurma) btnCreateTurma.addEventListener('click', clearTurmaForm);
|
||||||
|
if(btnCancelTurma) btnCancelTurma.addEventListener('click', clearTurmaForm);
|
||||||
|
|
||||||
|
if (btnSaveTurmaConfig) {
|
||||||
|
btnSaveTurmaConfig.addEventListener('click', async () => {
|
||||||
|
const nome = configTurmaNome.value.trim();
|
||||||
|
if(!nome) { showCustomAlert('Aviso', 'O Nome da Turma é obrigatório.'); return; }
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
nome,
|
||||||
|
sala: configTurmaSala.value,
|
||||||
|
periodo: configTurmaPeriodo.value,
|
||||||
|
profTitular: configTurmaProfTitular.value,
|
||||||
|
profAux1: configTurmaProfAux1.value,
|
||||||
|
profAux2: configTurmaProfAux2.value,
|
||||||
|
cuidadora: configTurmaCuidadora.value
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = configTurmaId.value;
|
||||||
|
const url = id ? '/api/turmas/' + id : '/api/turmas';
|
||||||
|
const method = id ? 'PUT' : 'POST';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
if(res.ok) {
|
||||||
|
showCustomAlert('Sucesso', 'Turma salva com sucesso!');
|
||||||
|
await fetchTurmas();
|
||||||
|
renderTurmasList();
|
||||||
|
clearTurmaForm();
|
||||||
|
} else {
|
||||||
|
showCustomAlert('Erro', 'Falha ao salvar turma.');
|
||||||
|
}
|
||||||
|
} catch(e) { console.error(e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alunos Logic
|
||||||
|
const clearChildForm = () => {
|
||||||
|
cFormId.value = '';
|
||||||
|
cFormTurma.value = filterAlunosTurma ? filterAlunosTurma.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';
|
||||||
|
};
|
||||||
|
|
||||||
|
if (cFormEspecial) {
|
||||||
|
cFormEspecial.addEventListener('change', () => {
|
||||||
|
divEspecialDetalhes.style.display = cFormEspecial.checked ? 'flex' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderAlunosList = () => {
|
||||||
|
if (!childrenList) return;
|
||||||
|
const filterTurmaId = filterAlunosTurma ? filterAlunosTurma.value : '';
|
||||||
|
let filtered = currentAlunos;
|
||||||
|
if(filterTurmaId) {
|
||||||
|
filtered = currentAlunos.filter(a => a.turma_id === filterTurmaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filtered.length === 0) {
|
||||||
|
childrenList.innerHTML = '<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Nenhuma criança encontrada.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered.sort((a, b) => (a.nome || '').localeCompare(b.nome || ''));
|
||||||
|
|
||||||
|
childrenList.innerHTML = filtered.map(c => {
|
||||||
|
const turmaObj = currentTurmas.find(t => t.id === c.turma_id);
|
||||||
|
const turmaNome = turmaObj ? turmaObj.nome : 'Sem turma';
|
||||||
|
return \`
|
||||||
|
<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(turmaNome)} \${c.apelido ? '- ' + escapeHtml(c.apelido) : ''}</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('');
|
||||||
|
};
|
||||||
|
|
||||||
|
if(filterAlunosTurma) {
|
||||||
|
filterAlunosTurma.addEventListener('change', () => {
|
||||||
|
renderAlunosList();
|
||||||
|
if(cFormTurma) cFormTurma.value = filterAlunosTurma.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.editChild = (id) => {
|
||||||
|
const c = currentAlunos.find(x => x.id === id);
|
||||||
|
if (!c) return;
|
||||||
|
|
||||||
|
cFormId.value = c.id;
|
||||||
|
cFormTurma.value = c.turma_id || '';
|
||||||
|
cFormNome.value = c.nome || '';
|
||||||
|
cFormApelido.value = c.apelido || '';
|
||||||
|
if(c.data_nasc) {
|
||||||
|
cFormDataNasc.value = c.data_nasc.split('T')[0];
|
||||||
|
} else {
|
||||||
|
cFormDataNasc.value = '';
|
||||||
|
}
|
||||||
|
cFormEspecial.checked = !!c.especial;
|
||||||
|
cFormEspecialDetalhes.value = c.especial_detalhes || '';
|
||||||
|
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.', true);
|
||||||
|
if (isOk) {
|
||||||
|
await fetch('/api/alunos/' + id, { method: 'DELETE' });
|
||||||
|
await fetchAlunos();
|
||||||
|
renderAlunosList();
|
||||||
|
clearChildForm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (btnMinhaTurma) {
|
||||||
|
btnMinhaTurma.addEventListener('click', async () => {
|
||||||
|
minhaTurmaModal.style.display = 'flex';
|
||||||
|
await fetchTurmas();
|
||||||
|
await fetchAlunos();
|
||||||
|
renderTurmasList();
|
||||||
|
renderAlunosList();
|
||||||
|
clearTurmaForm();
|
||||||
|
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', async () => {
|
||||||
|
if (!cFormNome.value.trim() || !cFormTurma.value) {
|
||||||
|
showCustomAlert('Aviso', 'O Nome da Criança e a Turma são obrigatórios.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = cFormId.value;
|
||||||
|
const body = {
|
||||||
|
turma_id: cFormTurma.value,
|
||||||
|
nome: cFormNome.value.trim(),
|
||||||
|
apelido: cFormApelido.value.trim(),
|
||||||
|
data_nasc: cFormDataNasc.value || null,
|
||||||
|
especial: cFormEspecial.checked,
|
||||||
|
especial_detalhes: cFormEspecialDetalhes.value.trim(),
|
||||||
|
pais: cFormPais.value.trim(),
|
||||||
|
autorizados: cFormAutorizados.value.trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = id ? '/api/alunos/' + id : '/api/alunos';
|
||||||
|
const method = id ? 'PUT' : 'POST';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
if(res.ok) {
|
||||||
|
await fetchAlunos();
|
||||||
|
renderAlunosList();
|
||||||
|
clearChildForm();
|
||||||
|
showCustomAlert('Sucesso', 'Criança salva com sucesso!');
|
||||||
|
} else {
|
||||||
|
showCustomAlert('Erro', 'Falha ao salvar criança.');
|
||||||
|
}
|
||||||
|
} catch(e) { console.error(e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
"nome", "apelido", "data_nasc" (formato YYYY-MM-DD), "especial" (boolean), "especial_detalhes", "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;
|
||||||
|
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.nome) cFormNome.value = parsed.nome;
|
||||||
|
if (parsed.apelido) cFormApelido.value = parsed.apelido;
|
||||||
|
if (parsed.data_nasc) cFormDataNasc.value = parsed.data_nasc;
|
||||||
|
if (parsed.especial) cFormEspecial.checked = parsed.especial;
|
||||||
|
divEspecialDetalhes.style.display = cFormEspecial.checked ? 'flex' : 'none';
|
||||||
|
if (parsed.especial_detalhes) cFormEspecialDetalhes.value = parsed.especial_detalhes;
|
||||||
|
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');
|
||||||
|
modal.className = 'settings-modal';
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
modal.innerHTML = \`
|
||||||
|
<div class="settings-modal-content obs-modal-content" style="max-width: 680px;">
|
||||||
|
<div class="settings-modal-header">
|
||||||
|
<h3>📋 Observação</h3>
|
||||||
|
<button class="btn-close-modal" onclick="this.closest('.settings-modal').remove()">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 20px; overflow-y: auto; max-height: 70vh;">
|
||||||
|
<div class="obs-detail-content" style="color: var(--text-primary); line-height: 1.6;">
|
||||||
|
\${marked.parse(mdText.replace(/^---[\\s\\S]*?---\\n/, ''))}
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 16px; display: flex; gap: 8px;">
|
||||||
|
<button class="btn-save-report" onclick="navigator.clipboard.writeText(this.closest('.settings-modal').querySelector('.obs-detail-content').innerText).then(()=>alert('Copiado!'))">Copiar Texto</button>
|
||||||
|
<button class="btn-discard" onclick="this.closest('.settings-modal').remove()">Fechar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
modal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === modal) modal.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// `;
|
||||||
|
|
||||||
|
appCode = appCode.slice(0, startIndex) + newLogic + appCode.slice(endIndex);
|
||||||
|
|
||||||
|
fs.writeFileSync(appFile, appCode);
|
||||||
+297
@@ -0,0 +1,297 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const serverFile = path.join(__dirname, 'server.js');
|
||||||
|
let serverCode = fs.readFileSync(serverFile, 'utf8');
|
||||||
|
|
||||||
|
// Add tables to initDb
|
||||||
|
const tablesSql = `
|
||||||
|
-- Turmas
|
||||||
|
CREATE TABLE IF NOT EXISTS escola.turmas (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
||||||
|
nome VARCHAR(255) NOT NULL,
|
||||||
|
sala VARCHAR(255),
|
||||||
|
periodo VARCHAR(100),
|
||||||
|
prof_titular VARCHAR(255),
|
||||||
|
prof_aux1 VARCHAR(255),
|
||||||
|
prof_aux2 VARCHAR(255),
|
||||||
|
cuidadora VARCHAR(255),
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Alunos
|
||||||
|
CREATE TABLE IF NOT EXISTS escola.alunos (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
||||||
|
turma_id UUID REFERENCES escola.turmas(id) ON DELETE CASCADE,
|
||||||
|
nome VARCHAR(255) NOT NULL,
|
||||||
|
apelido VARCHAR(255),
|
||||||
|
data_nasc DATE,
|
||||||
|
especial BOOLEAN DEFAULT false,
|
||||||
|
especial_detalhes TEXT,
|
||||||
|
pais TEXT,
|
||||||
|
autorizados TEXT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!serverCode.includes('CREATE TABLE IF NOT EXISTS escola.turmas')) {
|
||||||
|
serverCode = serverCode.replace(
|
||||||
|
"CREATE TABLE IF NOT EXISTS escola.estudio_musicas",
|
||||||
|
tablesSql + "\n CREATE TABLE IF NOT EXISTS escola.estudio_musicas"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add APIs before app.listen
|
||||||
|
const apiRoutes = `
|
||||||
|
// ============================================================
|
||||||
|
// CRUD TURMAS E ALUNOS (MINHA TURMA)
|
||||||
|
// ============================================================
|
||||||
|
app.get('/api/turmas', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query("SELECT * FROM escola.turmas WHERE usuario_id = $1 ORDER BY created_at ASC;", [usuarioId]);
|
||||||
|
res.json(dbRes.rows);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/turmas', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora } = req.body;
|
||||||
|
if (!nome) return res.status(400).json({ error: 'Nome é obrigatório' });
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query(
|
||||||
|
"INSERT INTO escola.turmas (usuario_id, nome, sala, periodo, prof_titular, prof_aux1, prof_aux2, cuidadora) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;",
|
||||||
|
[usuarioId, nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora]
|
||||||
|
);
|
||||||
|
res.status(201).json(dbRes.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.put('/api/turmas/:id', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { id } = req.params;
|
||||||
|
const { nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora } = req.body;
|
||||||
|
if (!nome) return res.status(400).json({ error: 'Nome é obrigatório' });
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query(
|
||||||
|
"UPDATE escola.turmas SET nome = $1, sala = $2, periodo = $3, prof_titular = $4, prof_aux1 = $5, prof_aux2 = $6, cuidadora = $7 WHERE id = $8 AND usuario_id = $9 RETURNING *;",
|
||||||
|
[nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora, id, usuarioId]
|
||||||
|
);
|
||||||
|
res.json(dbRes.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/api/turmas/:id', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { id } = req.params;
|
||||||
|
try {
|
||||||
|
await dbPool.query("DELETE FROM escola.turmas WHERE id = $1 AND usuario_id = $2;", [id, usuarioId]);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/alunos', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { turma_id } = req.query;
|
||||||
|
try {
|
||||||
|
let query = "SELECT * FROM escola.alunos WHERE usuario_id = $1";
|
||||||
|
let params = [usuarioId];
|
||||||
|
if (turma_id) {
|
||||||
|
query += " AND turma_id = $2";
|
||||||
|
params.push(turma_id);
|
||||||
|
}
|
||||||
|
query += " ORDER BY nome ASC;";
|
||||||
|
const dbRes = await dbPool.query(query, params);
|
||||||
|
res.json(dbRes.rows);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/alunos', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados } = req.body;
|
||||||
|
if (!nome || !turma_id) return res.status(400).json({ error: 'Nome e Turma são obrigatórios' });
|
||||||
|
let nascDate = data_nasc ? data_nasc : null;
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query(
|
||||||
|
"INSERT INTO escola.alunos (usuario_id, turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;",
|
||||||
|
[usuarioId, turma_id, nome, apelido, nascDate, especial, especial_detalhes, pais, autorizados]
|
||||||
|
);
|
||||||
|
res.status(201).json(dbRes.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.put('/api/alunos/:id', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { id } = req.params;
|
||||||
|
const { turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados } = req.body;
|
||||||
|
if (!nome || !turma_id) return res.status(400).json({ error: 'Nome e Turma são obrigatórios' });
|
||||||
|
let nascDate = data_nasc ? data_nasc : null;
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query(
|
||||||
|
"UPDATE escola.alunos SET turma_id = $1, nome = $2, apelido = $3, data_nasc = $4, especial = $5, especial_detalhes = $6, pais = $7, autorizados = $8 WHERE id = $9 AND usuario_id = $10 RETURNING *;",
|
||||||
|
[turma_id, nome, apelido, nascDate, especial, especial_detalhes, pais, autorizados, id, usuarioId]
|
||||||
|
);
|
||||||
|
res.json(dbRes.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/api/alunos/:id', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { id } = req.params;
|
||||||
|
try {
|
||||||
|
await dbPool.query("DELETE FROM escola.alunos WHERE id = $1 AND usuario_id = $2;", [id, usuarioId]);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});\n\n`;
|
||||||
|
|
||||||
|
if (!serverCode.includes('app.get(\'/api/turmas\'')) {
|
||||||
|
serverCode = serverCode.replace("app.listen(PORT", apiRoutes + "app.listen(PORT");
|
||||||
|
}
|
||||||
|
fs.writeFileSync(serverFile, serverCode);
|
||||||
|
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Update index.html
|
||||||
|
// ==========================================
|
||||||
|
const indexFile = path.join(__dirname, 'public', 'index.html');
|
||||||
|
let indexCode = fs.readFileSync(indexFile, 'utf8');
|
||||||
|
|
||||||
|
const newTurmaConfigHtml = `
|
||||||
|
<!-- SECÃO 1: TURMAS (Múltiplas turmas) -->
|
||||||
|
<div id="turmaConfigSection" style="display: flex; flex-direction: row; gap: 16px; flex: 1; height: 100%; overflow: hidden;">
|
||||||
|
<!-- Lado Esquerdo: Lista de Turmas -->
|
||||||
|
<div style="flex: 1; border-right: 1px solid var(--border-light); padding-right: 16px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; height: 100%;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
|
<h4 style="margin: 0; color: var(--text-primary);">Minhas Turmas</h4>
|
||||||
|
<button id="btnCreateTurma" style="background: var(--brand-green); color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; font-weight: 600; cursor: pointer;">+ Nova Turma</button>
|
||||||
|
</div>
|
||||||
|
<div id="turmasList" style="display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Carregando turmas...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lado Direito: Form Turma -->
|
||||||
|
<div id="turmaFormContainer" style="flex: 1.5; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; padding-left: 4px;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
|
<h4 id="turmaFormTitle" style="margin: 0; color: var(--text-primary);">Cadastrar Turma</h4>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="configTurmaId">
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
|
||||||
|
<div class="settings-group">
|
||||||
|
<label>Nome da Turma</label>
|
||||||
|
<input type="text" id="configTurmaNome" placeholder="Ex: Berçário 2" class="obs-input" style="width: 100%;">
|
||||||
|
</div>
|
||||||
|
<div class="settings-group">
|
||||||
|
<label>Sala</label>
|
||||||
|
<input type="text" id="configTurmaSala" placeholder="Ex: Sala 4" class="obs-input" style="width: 100%;">
|
||||||
|
</div>
|
||||||
|
<div class="settings-group">
|
||||||
|
<label>Período</label>
|
||||||
|
<select id="configTurmaPeriodo" class="obs-select" style="width: 100%;">
|
||||||
|
<option value="Integral">Integral</option>
|
||||||
|
<option value="Matutino">Matutino</option>
|
||||||
|
<option value="Vespertino">Vespertino</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; border-top: 1px solid var(--border-light); padding-top: 16px;">
|
||||||
|
<div class="settings-group">
|
||||||
|
<label>Professora Titular</label>
|
||||||
|
<input type="text" id="configTurmaProfTitular" placeholder="Sua Titulação e Nome" class="obs-input" style="width: 100%;">
|
||||||
|
</div>
|
||||||
|
<div class="settings-group">
|
||||||
|
<label>Professora Auxiliar 1</label>
|
||||||
|
<input type="text" id="configTurmaProfAux1" placeholder="Nome da Prof. Auxiliar" class="obs-input" style="width: 100%;">
|
||||||
|
</div>
|
||||||
|
<div class="settings-group">
|
||||||
|
<label>Professora Auxiliar 2</label>
|
||||||
|
<input type="text" id="configTurmaProfAux2" placeholder="Nome da Prof. Auxiliar (Opcional)" class="obs-input" style="width: 100%;">
|
||||||
|
</div>
|
||||||
|
<div class="settings-group">
|
||||||
|
<label>Cuidadora</label>
|
||||||
|
<input type="text" id="configTurmaCuidadora" placeholder="Nome da Cuidadora (Opcional)" class="obs-input" style="width: 100%;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; gap: 8px; justify-content: flex-end; border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: auto;">
|
||||||
|
<button id="btnCancelTurma" style="background: none; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 0.9rem;">Cancelar</button>
|
||||||
|
<button id="btnSaveTurmaConfig" style="background: var(--brand-green); border: none; color: white; padding: 10px 20px; border-radius: 8px; font-weight: 600; cursor: pointer; font-size: 0.95rem;">Salvar Turma</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const newTurmaAlunosHtml = `
|
||||||
|
<!-- SECÃO 2: ALUNOS DA TURMA -->
|
||||||
|
<div id="turmaAlunosSection" style="display: none; flex-direction: row; gap: 16px; flex: 1; height: 100%; overflow: hidden;">
|
||||||
|
<!-- Lado Esquerdo: Lista de Crianças e Filtro -->
|
||||||
|
<div style="flex: 1; border-right: 1px solid var(--border-light); padding-right: 16px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; height: 100%;">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<h4 style="margin: 0; color: var(--text-primary);">Lista de Crianças</h4>
|
||||||
|
<button id="btnCreateChild" style="background: var(--brand-green); color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: opacity 0.2s;">+ Nova Criança</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-group" style="margin-bottom: 0;">
|
||||||
|
<select id="filterAlunosTurma" class="obs-select" style="width: 100%; padding: 6px;">
|
||||||
|
<option value="">Todas as Turmas</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="childrenList" style="display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<!-- Carregado via JS -->
|
||||||
|
<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Carregando alunos...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lado Direito: Formulário de Cadastro/Edição -->
|
||||||
|
<div id="childFormContainer" style="flex: 1.5; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; padding-left: 4px;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
|
<h4 id="childFormTitle" style="margin: 0; color: var(--text-primary);">Cadastrar Criança</h4>
|
||||||
|
<button id="btnVoiceRecordChild" style="background: rgba(16, 163, 127, 0.1); border: 1px solid var(--brand-green); color: var(--brand-green); padding: 4px 8px; border-radius: 6px; font-size: 0.8rem; cursor: pointer; display: flex; align-items: center; gap: 4px;">
|
||||||
|
🎤 Preencher com Voz
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="childFormId">
|
||||||
|
|
||||||
|
<div class="settings-group" style="gap: 4px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;">Turma do Aluno</label>
|
||||||
|
<select id="childFormTurma" class="obs-select" style="width: 100%;"></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group" style="gap: 4px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;">Nome Completo da Criança</label>
|
||||||
|
<input type="text" id="childFormNome" placeholder="Ex: João da Silva" class="obs-input" style="width: 100%;">
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Only replace if we haven't already
|
||||||
|
if (indexCode.includes('id="turmaConfigSection" style="display: flex; flex-direction: column;')) {
|
||||||
|
const regexConfig = /<div id="turmaConfigSection".*?<\/div>\s*<\/div>\s*<!-- SECÃO 2: ALUNOS DA TURMA -->/s;
|
||||||
|
indexCode = indexCode.replace(regexConfig, newTurmaConfigHtml + "\n\n <!-- SECÃO 2: ALUNOS DA TURMA -->");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!indexCode.includes('id="filterAlunosTurma"')) {
|
||||||
|
const regexAlunos = /<!-- SECÃO 2: ALUNOS DA TURMA -->.*?<div class="settings-group" style="gap: 4px;">\s*<label.*?Nome Completo da Criança/s;
|
||||||
|
indexCode = indexCode.replace(regexAlunos, newTurmaAlunosHtml + "\n <div class=\"settings-group\" style=\"gap: 4px;\">\n <label style=\"color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;\">Nome Completo da Criança");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(indexFile, indexCode);
|
||||||
+230
-115
@@ -2989,19 +2989,36 @@ const initApp = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// MODAL: MINHA TURMA
|
// MODAL: MINHA TURMA (COM MÚLTIPLAS TURMAS E SUPABASE)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const minhaTurmaModal = document.getElementById('minhaTurmaModal');
|
const minhaTurmaModal = document.getElementById('minhaTurmaModal');
|
||||||
const btnMinhaTurma = document.getElementById('btnMinhaTurma');
|
const btnMinhaTurma = document.getElementById('btnMinhaTurma');
|
||||||
const btnCloseMinhaTurmaModal = document.getElementById('btnCloseMinhaTurmaModal');
|
const btnCloseMinhaTurmaModal = document.getElementById('btnCloseMinhaTurmaModal');
|
||||||
|
|
||||||
|
// Turmas List & Form
|
||||||
|
const turmasList = document.getElementById('turmasList');
|
||||||
|
const btnCreateTurma = document.getElementById('btnCreateTurma');
|
||||||
|
const btnCancelTurma = document.getElementById('btnCancelTurma');
|
||||||
|
const btnSaveTurmaConfig = document.getElementById('btnSaveTurmaConfig');
|
||||||
|
const configTurmaId = document.getElementById('configTurmaId');
|
||||||
|
const configTurmaNome = document.getElementById('configTurmaNome');
|
||||||
|
const configTurmaSala = document.getElementById('configTurmaSala');
|
||||||
|
const configTurmaPeriodo = document.getElementById('configTurmaPeriodo');
|
||||||
|
const configTurmaProfTitular = document.getElementById('configTurmaProfTitular');
|
||||||
|
const configTurmaProfAux1 = document.getElementById('configTurmaProfAux1');
|
||||||
|
const configTurmaProfAux2 = document.getElementById('configTurmaProfAux2');
|
||||||
|
const configTurmaCuidadora = document.getElementById('configTurmaCuidadora');
|
||||||
|
const turmaFormTitle = document.getElementById('turmaFormTitle');
|
||||||
|
|
||||||
|
// Alunos List & Form
|
||||||
|
const filterAlunosTurma = document.getElementById('filterAlunosTurma');
|
||||||
const childrenList = document.getElementById('childrenList');
|
const childrenList = document.getElementById('childrenList');
|
||||||
const btnCreateChild = document.getElementById('btnCreateChild');
|
const btnCreateChild = document.getElementById('btnCreateChild');
|
||||||
const btnCancelChild = document.getElementById('btnCancelChild');
|
const btnCancelChild = document.getElementById('btnCancelChild');
|
||||||
const btnSaveChild = document.getElementById('btnSaveChild');
|
const btnSaveChild = document.getElementById('btnSaveChild');
|
||||||
const childFormTitle = document.getElementById('childFormTitle');
|
const childFormTitle = document.getElementById('childFormTitle');
|
||||||
|
|
||||||
// Fields
|
|
||||||
const cFormId = document.getElementById('childFormId');
|
const cFormId = document.getElementById('childFormId');
|
||||||
|
const cFormTurma = document.getElementById('childFormTurma');
|
||||||
const cFormNome = document.getElementById('childFormNome');
|
const cFormNome = document.getElementById('childFormNome');
|
||||||
const cFormApelido = document.getElementById('childFormApelido');
|
const cFormApelido = document.getElementById('childFormApelido');
|
||||||
const cFormDataNasc = document.getElementById('childFormDataNasc');
|
const cFormDataNasc = document.getElementById('childFormDataNasc');
|
||||||
@@ -3011,22 +3028,45 @@ const initApp = () => {
|
|||||||
const cFormPais = document.getElementById('childFormPais');
|
const cFormPais = document.getElementById('childFormPais');
|
||||||
const cFormAutorizados = document.getElementById('childFormAutorizados');
|
const cFormAutorizados = document.getElementById('childFormAutorizados');
|
||||||
|
|
||||||
// Fields Turma Config
|
// Tabs
|
||||||
const tabTurmaConfig = document.getElementById('tabTurmaConfig');
|
const tabTurmaConfig = document.getElementById('tabTurmaConfig');
|
||||||
const tabTurmaAlunos = document.getElementById('tabTurmaAlunos');
|
const tabTurmaAlunos = document.getElementById('tabTurmaAlunos');
|
||||||
const turmaConfigSection = document.getElementById('turmaConfigSection');
|
const turmaConfigSection = document.getElementById('turmaConfigSection');
|
||||||
const turmaAlunosSection = document.getElementById('turmaAlunosSection');
|
const turmaAlunosSection = document.getElementById('turmaAlunosSection');
|
||||||
|
|
||||||
const configTurmaNome = document.getElementById('configTurmaNome');
|
let currentTurmas = [];
|
||||||
const configTurmaSala = document.getElementById('configTurmaSala');
|
let currentAlunos = [];
|
||||||
const configTurmaPeriodo = document.getElementById('configTurmaPeriodo');
|
|
||||||
const configTurmaProfTitular = document.getElementById('configTurmaProfTitular');
|
|
||||||
const configTurmaProfAux1 = document.getElementById('configTurmaProfAux1');
|
|
||||||
const configTurmaProfAux2 = document.getElementById('configTurmaProfAux2');
|
|
||||||
const configTurmaCuidadora = document.getElementById('configTurmaCuidadora');
|
|
||||||
const btnSaveTurmaConfig = document.getElementById('btnSaveTurmaConfig');
|
|
||||||
|
|
||||||
// Tab Logic
|
// API Calls
|
||||||
|
const fetchTurmas = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/turmas');
|
||||||
|
currentTurmas = await res.json();
|
||||||
|
populateTurmaSelects();
|
||||||
|
return currentTurmas;
|
||||||
|
} catch(e) { console.error(e); return []; }
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchAlunos = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/alunos');
|
||||||
|
currentAlunos = await res.json();
|
||||||
|
localStorage.setItem('camila_turma', JSON.stringify(currentAlunos));
|
||||||
|
return currentAlunos;
|
||||||
|
} catch(e) { console.error(e); return []; }
|
||||||
|
};
|
||||||
|
|
||||||
|
const populateTurmaSelects = () => {
|
||||||
|
const opts = '<option value="">Todas as Turmas</option>' + currentTurmas.map(t => `<option value="${t.id}">${t.nome}</option>`).join('');
|
||||||
|
if(filterAlunosTurma) filterAlunosTurma.innerHTML = opts;
|
||||||
|
const formOpts = '<option value="">Selecione uma turma...</option>' + currentTurmas.map(t => `<option value="${t.id}">${t.nome}</option>`).join('');
|
||||||
|
if(cFormTurma) cFormTurma.innerHTML = formOpts;
|
||||||
|
if(document.getElementById('emitirFilterTurma')) {
|
||||||
|
document.getElementById('emitirFilterTurma').innerHTML = '<option value="">Todas as turmas</option>' + currentTurmas.map(t => `<option value="${t.id}">${t.nome}</option>`).join('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tabs Logic
|
||||||
if (tabTurmaConfig) {
|
if (tabTurmaConfig) {
|
||||||
tabTurmaConfig.addEventListener('click', () => {
|
tabTurmaConfig.addEventListener('click', () => {
|
||||||
tabTurmaConfig.classList.add('active');
|
tabTurmaConfig.classList.add('active');
|
||||||
@@ -3052,86 +3092,109 @@ const initApp = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle Especial Fields
|
// Turmas Logic
|
||||||
if (cFormEspecial) {
|
const clearTurmaForm = () => {
|
||||||
cFormEspecial.addEventListener('change', () => {
|
configTurmaId.value = '';
|
||||||
if (cFormEspecial.checked) {
|
configTurmaNome.value = '';
|
||||||
divEspecialDetalhes.style.display = 'flex';
|
configTurmaSala.value = '';
|
||||||
} else {
|
configTurmaPeriodo.value = 'Integral';
|
||||||
divEspecialDetalhes.style.display = 'none';
|
configTurmaProfTitular.value = '';
|
||||||
|
configTurmaProfAux1.value = '';
|
||||||
|
configTurmaProfAux2.value = '';
|
||||||
|
configTurmaCuidadora.value = '';
|
||||||
|
turmaFormTitle.textContent = 'Cadastrar Turma';
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTurmasList = () => {
|
||||||
|
if(!turmasList) return;
|
||||||
|
if(currentTurmas.length === 0) {
|
||||||
|
turmasList.innerHTML = '<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Nenhuma turma cadastrada.</div>';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
turmasList.innerHTML = currentTurmas.map(t => `
|
||||||
|
<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(t.nome)}</div>
|
||||||
|
<div style="font-size: 0.8rem; color: var(--text-secondary);">${t.periodo || ''} ${t.sala ? '- ' + t.sala : ''}</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px;">
|
||||||
|
<button onclick="editTurma('${t.id}')" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px;" title="Editar">✏️</button>
|
||||||
|
<button onclick="deleteTurma('${t.id}')" style="background: none; border: none; color: #ef4444; cursor: pointer; padding: 4px;" title="Excluir">🗑️</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.editTurma = (id) => {
|
||||||
|
const t = currentTurmas.find(x => x.id === id);
|
||||||
|
if(!t) return;
|
||||||
|
configTurmaId.value = t.id;
|
||||||
|
configTurmaNome.value = t.nome || '';
|
||||||
|
configTurmaSala.value = t.sala || '';
|
||||||
|
configTurmaPeriodo.value = t.periodo || 'Integral';
|
||||||
|
configTurmaProfTitular.value = t.prof_titular || '';
|
||||||
|
configTurmaProfAux1.value = t.prof_aux1 || '';
|
||||||
|
configTurmaProfAux2.value = t.prof_aux2 || '';
|
||||||
|
configTurmaCuidadora.value = t.cuidadora || '';
|
||||||
|
turmaFormTitle.textContent = 'Editar Turma';
|
||||||
|
};
|
||||||
|
|
||||||
|
window.deleteTurma = async (id) => {
|
||||||
|
const isOk = await showCustomConfirm('Excluir Turma', 'Deseja excluir esta turma? Todos os alunos associados a ela também serão excluídos.', true);
|
||||||
|
if(isOk) {
|
||||||
|
await fetch('/api/turmas/' + id, { method: 'DELETE' });
|
||||||
|
await fetchTurmas();
|
||||||
|
await fetchAlunos();
|
||||||
|
renderTurmasList();
|
||||||
|
renderAlunosList();
|
||||||
|
clearTurmaForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadTurma = () => {
|
|
||||||
return JSON.parse(localStorage.getItem('camila_turma') || '[]');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveTurma = (turma) => {
|
if(btnCreateTurma) btnCreateTurma.addEventListener('click', clearTurmaForm);
|
||||||
localStorage.setItem('camila_turma', JSON.stringify(turma));
|
if(btnCancelTurma) btnCancelTurma.addEventListener('click', clearTurmaForm);
|
||||||
};
|
|
||||||
|
|
||||||
const loadTurmaConfig = () => {
|
|
||||||
return JSON.parse(localStorage.getItem('camila_config_turma') || '{}');
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveTurmaConfig = (cfg) => {
|
|
||||||
localStorage.setItem('camila_config_turma', JSON.stringify(cfg));
|
|
||||||
showCustomAlert('Sucesso', 'Configurações da turma salvas com sucesso!');
|
|
||||||
};
|
|
||||||
|
|
||||||
if (btnSaveTurmaConfig) {
|
if (btnSaveTurmaConfig) {
|
||||||
btnSaveTurmaConfig.addEventListener('click', () => {
|
btnSaveTurmaConfig.addEventListener('click', async () => {
|
||||||
saveTurmaConfig({
|
const nome = configTurmaNome.value.trim();
|
||||||
nome: configTurmaNome.value,
|
if(!nome) { showCustomAlert('Aviso', 'O Nome da Turma é obrigatório.'); return; }
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
nome,
|
||||||
sala: configTurmaSala.value,
|
sala: configTurmaSala.value,
|
||||||
periodo: configTurmaPeriodo.value,
|
periodo: configTurmaPeriodo.value,
|
||||||
profTitular: configTurmaProfTitular.value,
|
profTitular: configTurmaProfTitular.value,
|
||||||
profAux1: configTurmaProfAux1.value,
|
profAux1: configTurmaProfAux1.value,
|
||||||
profAux2: configTurmaProfAux2.value,
|
profAux2: configTurmaProfAux2.value,
|
||||||
cuidadora: configTurmaCuidadora.value
|
cuidadora: configTurmaCuidadora.value
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = configTurmaId.value;
|
||||||
|
const url = id ? '/api/turmas/' + id : '/api/turmas';
|
||||||
|
const method = id ? 'PUT' : 'POST';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
|
if(res.ok) {
|
||||||
|
showCustomAlert('Sucesso', 'Turma salva com sucesso!');
|
||||||
|
await fetchTurmas();
|
||||||
|
renderTurmasList();
|
||||||
|
clearTurmaForm();
|
||||||
|
} else {
|
||||||
|
showCustomAlert('Erro', 'Falha ao salvar turma.');
|
||||||
|
}
|
||||||
|
} catch(e) { console.error(e); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const populateTurmaConfig = () => {
|
// Alunos Logic
|
||||||
const cfg = loadTurmaConfig();
|
|
||||||
if(configTurmaNome) configTurmaNome.value = cfg.nome || '';
|
|
||||||
if(configTurmaSala) configTurmaSala.value = cfg.sala || '';
|
|
||||||
if(configTurmaPeriodo) configTurmaPeriodo.value = cfg.periodo || 'Integral';
|
|
||||||
if(configTurmaProfTitular) configTurmaProfTitular.value = cfg.profTitular || '';
|
|
||||||
if(configTurmaProfAux1) configTurmaProfAux1.value = cfg.profAux1 || '';
|
|
||||||
if(configTurmaProfAux2) configTurmaProfAux2.value = cfg.profAux2 || '';
|
|
||||||
if(configTurmaCuidadora) configTurmaCuidadora.value = cfg.cuidadora || '';
|
|
||||||
};
|
|
||||||
|
|
||||||
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.apelido || '')}</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 = () => {
|
const clearChildForm = () => {
|
||||||
cFormId.value = '';
|
cFormId.value = '';
|
||||||
|
cFormTurma.value = filterAlunosTurma ? filterAlunosTurma.value : '';
|
||||||
cFormNome.value = '';
|
cFormNome.value = '';
|
||||||
cFormApelido.value = '';
|
cFormApelido.value = '';
|
||||||
cFormDataNasc.value = '';
|
cFormDataNasc.value = '';
|
||||||
@@ -3143,17 +3206,66 @@ const initApp = () => {
|
|||||||
childFormTitle.textContent = 'Cadastrar Criança';
|
childFormTitle.textContent = 'Cadastrar Criança';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (cFormEspecial) {
|
||||||
|
cFormEspecial.addEventListener('change', () => {
|
||||||
|
divEspecialDetalhes.style.display = cFormEspecial.checked ? 'flex' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderAlunosList = () => {
|
||||||
|
if (!childrenList) return;
|
||||||
|
const filterTurmaId = filterAlunosTurma ? filterAlunosTurma.value : '';
|
||||||
|
let filtered = currentAlunos;
|
||||||
|
if(filterTurmaId) {
|
||||||
|
filtered = currentAlunos.filter(a => a.turma_id === filterTurmaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filtered.length === 0) {
|
||||||
|
childrenList.innerHTML = '<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Nenhuma criança encontrada.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered.sort((a, b) => (a.nome || '').localeCompare(b.nome || ''));
|
||||||
|
|
||||||
|
childrenList.innerHTML = filtered.map(c => {
|
||||||
|
const turmaObj = currentTurmas.find(t => t.id === c.turma_id);
|
||||||
|
const turmaNome = turmaObj ? turmaObj.nome : 'Sem turma';
|
||||||
|
return `
|
||||||
|
<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(turmaNome)} ${c.apelido ? '- ' + escapeHtml(c.apelido) : ''}</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('');
|
||||||
|
};
|
||||||
|
|
||||||
|
if(filterAlunosTurma) {
|
||||||
|
filterAlunosTurma.addEventListener('change', () => {
|
||||||
|
renderAlunosList();
|
||||||
|
if(cFormTurma) cFormTurma.value = filterAlunosTurma.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.editChild = (id) => {
|
window.editChild = (id) => {
|
||||||
const turma = loadTurma();
|
const c = currentAlunos.find(x => x.id === id);
|
||||||
const c = turma.find(x => x.id === id);
|
|
||||||
if (!c) return;
|
if (!c) return;
|
||||||
|
|
||||||
cFormId.value = c.id;
|
cFormId.value = c.id;
|
||||||
|
cFormTurma.value = c.turma_id || '';
|
||||||
cFormNome.value = c.nome || '';
|
cFormNome.value = c.nome || '';
|
||||||
cFormApelido.value = c.apelido || '';
|
cFormApelido.value = c.apelido || '';
|
||||||
cFormDataNasc.value = c.dataNasc || '';
|
if(c.data_nasc) {
|
||||||
|
cFormDataNasc.value = c.data_nasc.split('T')[0];
|
||||||
|
} else {
|
||||||
|
cFormDataNasc.value = '';
|
||||||
|
}
|
||||||
cFormEspecial.checked = !!c.especial;
|
cFormEspecial.checked = !!c.especial;
|
||||||
cFormEspecialDetalhes.value = c.especialDetalhes || '';
|
cFormEspecialDetalhes.value = c.especial_detalhes || '';
|
||||||
divEspecialDetalhes.style.display = c.especial ? 'flex' : 'none';
|
divEspecialDetalhes.style.display = c.especial ? 'flex' : 'none';
|
||||||
cFormPais.value = c.pais || '';
|
cFormPais.value = c.pais || '';
|
||||||
cFormAutorizados.value = c.autorizados || '';
|
cFormAutorizados.value = c.autorizados || '';
|
||||||
@@ -3162,21 +3274,23 @@ const initApp = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.deleteChild = async (id) => {
|
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);
|
const isOk = await showCustomConfirm('Excluir', 'Deseja excluir esta criança? Isso não removerá as observações já feitas.', true);
|
||||||
if (isOk) {
|
if (isOk) {
|
||||||
let turma = loadTurma();
|
await fetch('/api/alunos/' + id, { method: 'DELETE' });
|
||||||
turma = turma.filter(x => x.id !== id);
|
await fetchAlunos();
|
||||||
saveTurma(turma);
|
renderAlunosList();
|
||||||
renderTurmaList();
|
|
||||||
clearChildForm();
|
clearChildForm();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (btnMinhaTurma) {
|
if (btnMinhaTurma) {
|
||||||
btnMinhaTurma.addEventListener('click', () => {
|
btnMinhaTurma.addEventListener('click', async () => {
|
||||||
minhaTurmaModal.style.display = 'flex';
|
minhaTurmaModal.style.display = 'flex';
|
||||||
populateTurmaConfig();
|
await fetchTurmas();
|
||||||
renderTurmaList();
|
await fetchAlunos();
|
||||||
|
renderTurmasList();
|
||||||
|
renderAlunosList();
|
||||||
|
clearTurmaForm();
|
||||||
clearChildForm();
|
clearChildForm();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3192,42 +3306,48 @@ const initApp = () => {
|
|||||||
if (btnCancelChild) btnCancelChild.addEventListener('click', clearChildForm);
|
if (btnCancelChild) btnCancelChild.addEventListener('click', clearChildForm);
|
||||||
|
|
||||||
if (btnSaveChild) {
|
if (btnSaveChild) {
|
||||||
btnSaveChild.addEventListener('click', () => {
|
btnSaveChild.addEventListener('click', async () => {
|
||||||
if (!cFormNome.value.trim()) {
|
if (!cFormNome.value.trim() || !cFormTurma.value) {
|
||||||
showCustomAlert('Aviso', 'O Nome Completo é obrigatório.');
|
showCustomAlert('Aviso', 'O Nome da Criança e a Turma são obrigatórios.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let turma = loadTurma();
|
const id = cFormId.value;
|
||||||
const id = cFormId.value || 'child_' + Date.now();
|
const body = {
|
||||||
|
turma_id: cFormTurma.value,
|
||||||
const newChild = {
|
|
||||||
id,
|
|
||||||
nome: cFormNome.value.trim(),
|
nome: cFormNome.value.trim(),
|
||||||
apelido: cFormApelido.value.trim(),
|
apelido: cFormApelido.value.trim(),
|
||||||
dataNasc: cFormDataNasc.value,
|
data_nasc: cFormDataNasc.value || null,
|
||||||
especial: cFormEspecial.checked,
|
especial: cFormEspecial.checked,
|
||||||
especialDetalhes: cFormEspecialDetalhes.value.trim(),
|
especial_detalhes: cFormEspecialDetalhes.value.trim(),
|
||||||
pais: cFormPais.value.trim(),
|
pais: cFormPais.value.trim(),
|
||||||
autorizados: cFormAutorizados.value.trim()
|
autorizados: cFormAutorizados.value.trim()
|
||||||
};
|
};
|
||||||
|
|
||||||
const idx = turma.findIndex(x => x.id === id);
|
const url = id ? '/api/alunos/' + id : '/api/alunos';
|
||||||
if (idx > -1) {
|
const method = id ? 'PUT' : 'POST';
|
||||||
turma[idx] = newChild;
|
|
||||||
} else {
|
|
||||||
turma.push(newChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveTurma(turma);
|
try {
|
||||||
renderTurmaList();
|
const res = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
if(res.ok) {
|
||||||
|
await fetchAlunos();
|
||||||
|
renderAlunosList();
|
||||||
clearChildForm();
|
clearChildForm();
|
||||||
|
showCustomAlert('Sucesso', 'Criança salva com sucesso!');
|
||||||
|
} else {
|
||||||
|
showCustomAlert('Erro', 'Falha ao salvar criança.');
|
||||||
|
}
|
||||||
|
} catch(e) { console.error(e); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preencher com Voz
|
// Preencher com Voz
|
||||||
const btnVoiceRecordChild = document.getElementById('btnVoiceRecordChild');
|
const btnVoiceRecordChild = document.getElementById('btnVoiceRecordChild');
|
||||||
if (btnVoiceRecordChild && window.SpeechRecognition || window.webkitSpeechRecognition) {
|
if (btnVoiceRecordChild && (window.SpeechRecognition || window.webkitSpeechRecognition)) {
|
||||||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||||
const formRecognition = new SpeechRecognition();
|
const formRecognition = new SpeechRecognition();
|
||||||
formRecognition.lang = 'pt-BR';
|
formRecognition.lang = 'pt-BR';
|
||||||
@@ -3254,7 +3374,7 @@ const initApp = () => {
|
|||||||
role: 'user',
|
role: 'user',
|
||||||
content: `A professora ditou as seguintes informações para cadastrar uma criança na turma: "${transcript}".
|
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:
|
Por favor, extraia os dados e retorne APENAS um JSON válido com os seguintes campos:
|
||||||
"nome", "apelido", "dataNasc" (formato YYYY-MM-DD), "especial" (boolean), "especialDetalhes", "pais", "autorizados".
|
"nome", "apelido", "data_nasc" (formato YYYY-MM-DD), "especial" (boolean), "especial_detalhes", "pais", "autorizados".
|
||||||
Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
|
Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
|
||||||
}],
|
}],
|
||||||
temperature: 0.1
|
temperature: 0.1
|
||||||
@@ -3264,20 +3384,15 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
|
|||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const assistantReply = data.message;
|
const assistantReply = data.message;
|
||||||
// Extract JSON block
|
|
||||||
const match = assistantReply.match(/```json\n([\s\S]*?)\n```/) || assistantReply.match(/\{[\s\S]*\}/);
|
const match = assistantReply.match(/```json\n([\s\S]*?)\n```/) || assistantReply.match(/\{[\s\S]*\}/);
|
||||||
if (match) {
|
if (match) {
|
||||||
const parsed = JSON.parse(match[1] || match[0]);
|
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.nome) cFormNome.value = parsed.nome;
|
||||||
if (parsed.apelido) cFormApelido.value = parsed.apelido;
|
if (parsed.apelido) cFormApelido.value = parsed.apelido;
|
||||||
if (parsed.dataNasc) cFormDataNasc.value = parsed.dataNasc;
|
if (parsed.data_nasc) cFormDataNasc.value = parsed.data_nasc;
|
||||||
if (parsed.especial) cFormEspecial.checked = parsed.especial;
|
if (parsed.especial) cFormEspecial.checked = parsed.especial;
|
||||||
divEspecialDetalhes.style.display = cFormEspecial.checked ? 'flex' : 'none';
|
divEspecialDetalhes.style.display = cFormEspecial.checked ? 'flex' : 'none';
|
||||||
if (parsed.especialDetalhes) cFormEspecialDetalhes.value = parsed.especialDetalhes;
|
if (parsed.especial_detalhes) cFormEspecialDetalhes.value = parsed.especial_detalhes;
|
||||||
if (parsed.pais) cFormPais.value = parsed.pais;
|
if (parsed.pais) cFormPais.value = parsed.pais;
|
||||||
if (parsed.autorizados) cFormAutorizados.value = parsed.autorizados;
|
if (parsed.autorizados) cFormAutorizados.value = parsed.autorizados;
|
||||||
}
|
}
|
||||||
@@ -3336,7 +3451,7 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================
|
// // ============================================================
|
||||||
// CRUD DE MODELOS DE RELATÓRIO
|
// CRUD DE MODELOS DE RELATÓRIO
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const modelosRelatorioModal = document.getElementById('modelosRelatorioModal');
|
const modelosRelatorioModal = document.getElementById('modelosRelatorioModal');
|
||||||
|
|||||||
+44
-7
@@ -536,10 +536,26 @@
|
|||||||
<div class="settings-modal-body" style="padding: 16px; overflow: hidden; display: flex; flex-direction: column; flex: 1; min-height: 500px;">
|
<div class="settings-modal-body" style="padding: 16px; overflow: hidden; display: flex; flex-direction: column; flex: 1; min-height: 500px;">
|
||||||
|
|
||||||
<!-- SECÃO 1: CONFIGURAÇÕES DA TURMA -->
|
<!-- SECÃO 1: CONFIGURAÇÕES DA TURMA -->
|
||||||
<div id="turmaConfigSection" style="display: flex; flex-direction: column; gap: 16px; flex: 1; width: 100%; overflow-y: auto; padding-right: 8px;">
|
|
||||||
<h4 style="margin: 0; color: var(--text-primary);">Configurações e Equipe da Turma</h4>
|
|
||||||
<p style="margin: 0; font-size: 0.85rem; color: var(--text-secondary);">Defina as informações gerais da turma. Estas informações valem para todos os alunos e serão usadas nos relatórios.</p>
|
|
||||||
|
|
||||||
|
<!-- SECÃO 1: TURMAS (Múltiplas turmas) -->
|
||||||
|
<div id="turmaConfigSection" style="display: flex; flex-direction: row; gap: 16px; flex: 1; height: 100%; overflow: hidden;">
|
||||||
|
<!-- Lado Esquerdo: Lista de Turmas -->
|
||||||
|
<div style="flex: 1; border-right: 1px solid var(--border-light); padding-right: 16px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; height: 100%;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
|
<h4 style="margin: 0; color: var(--text-primary);">Minhas Turmas</h4>
|
||||||
|
<button id="btnCreateTurma" style="background: var(--brand-green); color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; font-weight: 600; cursor: pointer;">+ Nova Turma</button>
|
||||||
|
</div>
|
||||||
|
<div id="turmasList" style="display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Carregando turmas...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lado Direito: Form Turma -->
|
||||||
|
<div id="turmaFormContainer" style="flex: 1.5; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; padding-left: 4px;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
|
<h4 id="turmaFormTitle" style="margin: 0; color: var(--text-primary);">Cadastrar Turma</h4>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="configTurmaId">
|
||||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
|
||||||
<div class="settings-group">
|
<div class="settings-group">
|
||||||
<label>Nome da Turma</label>
|
<label>Nome da Turma</label>
|
||||||
@@ -579,21 +595,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; gap: 8px; justify-content: flex-end; border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: auto;">
|
<div style="display: flex; gap: 8px; justify-content: flex-end; border-top: 1px solid var(--border-light); padding-top: 16px; margin-top: auto;">
|
||||||
<button id="btnSaveTurmaConfig" style="background: var(--brand-green); border: none; color: white; padding: 10px 20px; border-radius: 8px; font-weight: 600; cursor: pointer; font-size: 0.95rem;">Salvar Dados da Turma</button>
|
<button id="btnCancelTurma" style="background: none; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: 0.9rem;">Cancelar</button>
|
||||||
|
<button id="btnSaveTurmaConfig" style="background: var(--brand-green); border: none; color: white; padding: 10px 20px; border-radius: 8px; font-weight: 600; cursor: pointer; font-size: 0.95rem;">Salvar Turma</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- SECÃO 2: ALUNOS DA TURMA -->
|
<!-- SECÃO 2: ALUNOS DA TURMA -->
|
||||||
<div id="turmaAlunosSection" style="display: none; flex-direction: row; gap: 16px; flex: 1; height: 100%; overflow: hidden;">
|
<div id="turmaAlunosSection" style="display: none; flex-direction: row; gap: 16px; flex: 1; height: 100%; overflow: hidden;">
|
||||||
<!-- Lado Esquerdo: Lista de Crianças -->
|
<!-- Lado Esquerdo: Lista de Crianças e Filtro -->
|
||||||
<div style="flex: 1; border-right: 1px solid var(--border-light); padding-right: 16px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; height: 100%;">
|
<div style="flex: 1; border-right: 1px solid var(--border-light); padding-right: 16px; display: flex; flex-direction: column; gap: 12px; overflow-y: auto; height: 100%;">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
<div style="display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
<h4 style="margin: 0; color: var(--text-primary);">Lista de Crianças</h4>
|
<h4 style="margin: 0; color: var(--text-primary);">Lista de Crianças</h4>
|
||||||
<button id="btnCreateChild" style="background: var(--brand-green); color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: opacity 0.2s;">+ Nova Criança</button>
|
<button id="btnCreateChild" style="background: var(--brand-green); color: white; border: none; padding: 6px 12px; border-radius: 6px; font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: opacity 0.2s;">+ Nova Criança</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-group" style="margin-bottom: 0;">
|
||||||
|
<select id="filterAlunosTurma" class="obs-select" style="width: 100%; padding: 6px;">
|
||||||
|
<option value="">Todas as Turmas</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="childrenList" style="display: flex; flex-direction: column; gap: 8px;">
|
<div id="childrenList" style="display: flex; flex-direction: column; gap: 8px;">
|
||||||
<!-- Carregado via JS -->
|
<!-- Carregado via JS -->
|
||||||
<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Carregando turma...</div>
|
<div style="color: var(--text-secondary); text-align: center; padding: 20px;">Carregando alunos...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -607,6 +634,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<input type="hidden" id="childFormId">
|
<input type="hidden" id="childFormId">
|
||||||
|
|
||||||
|
<div class="settings-group" style="gap: 4px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;">Turma do Aluno</label>
|
||||||
|
<select id="childFormTurma" class="obs-select" style="width: 100%;"></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group" style="gap: 4px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;">Nome Completo da Criança</label>
|
||||||
|
<input type="text" id="childFormNome" placeholder="Ex: João da Silva" class="obs-input" style="width: 100%;">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="settings-group" style="gap: 4px;">
|
<div class="settings-group" style="gap: 4px;">
|
||||||
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;">Nome Completo da Criança</label>
|
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 500;">Nome Completo da Criança</label>
|
||||||
<input type="text" id="childFormNome" placeholder="Ex: João da Silva" class="obs-input" style="width: 100%;">
|
<input type="text" id="childFormNome" placeholder="Ex: João da Silva" class="obs-input" style="width: 100%;">
|
||||||
|
|||||||
@@ -2097,6 +2097,36 @@ async function initDatabase() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
-- Estúdio Musical (Áudio MiniMax)
|
-- Estúdio Musical (Áudio MiniMax)
|
||||||
|
|
||||||
|
-- Turmas
|
||||||
|
CREATE TABLE IF NOT EXISTS escola.turmas (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
||||||
|
nome VARCHAR(255) NOT NULL,
|
||||||
|
sala VARCHAR(255),
|
||||||
|
periodo VARCHAR(100),
|
||||||
|
prof_titular VARCHAR(255),
|
||||||
|
prof_aux1 VARCHAR(255),
|
||||||
|
prof_aux2 VARCHAR(255),
|
||||||
|
cuidadora VARCHAR(255),
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Alunos
|
||||||
|
CREATE TABLE IF NOT EXISTS escola.alunos (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
||||||
|
turma_id UUID REFERENCES escola.turmas(id) ON DELETE CASCADE,
|
||||||
|
nome VARCHAR(255) NOT NULL,
|
||||||
|
apelido VARCHAR(255),
|
||||||
|
data_nasc DATE,
|
||||||
|
especial BOOLEAN DEFAULT false,
|
||||||
|
especial_detalhes TEXT,
|
||||||
|
pais TEXT,
|
||||||
|
autorizados TEXT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS escola.estudio_musicas (
|
CREATE TABLE IF NOT EXISTS escola.estudio_musicas (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
||||||
@@ -4541,6 +4571,124 @@ app.get('*', requireAuth, (req, res) => {
|
|||||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// CRUD TURMAS E ALUNOS (MINHA TURMA)
|
||||||
|
// ============================================================
|
||||||
|
app.get('/api/turmas', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query("SELECT * FROM escola.turmas WHERE usuario_id = $1 ORDER BY created_at ASC;", [usuarioId]);
|
||||||
|
res.json(dbRes.rows);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/turmas', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora } = req.body;
|
||||||
|
if (!nome) return res.status(400).json({ error: 'Nome é obrigatório' });
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query(
|
||||||
|
"INSERT INTO escola.turmas (usuario_id, nome, sala, periodo, prof_titular, prof_aux1, prof_aux2, cuidadora) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;",
|
||||||
|
[usuarioId, nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora]
|
||||||
|
);
|
||||||
|
res.status(201).json(dbRes.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.put('/api/turmas/:id', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { id } = req.params;
|
||||||
|
const { nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora } = req.body;
|
||||||
|
if (!nome) return res.status(400).json({ error: 'Nome é obrigatório' });
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query(
|
||||||
|
"UPDATE escola.turmas SET nome = $1, sala = $2, periodo = $3, prof_titular = $4, prof_aux1 = $5, prof_aux2 = $6, cuidadora = $7 WHERE id = $8 AND usuario_id = $9 RETURNING *;",
|
||||||
|
[nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora, id, usuarioId]
|
||||||
|
);
|
||||||
|
res.json(dbRes.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/api/turmas/:id', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { id } = req.params;
|
||||||
|
try {
|
||||||
|
await dbPool.query("DELETE FROM escola.turmas WHERE id = $1 AND usuario_id = $2;", [id, usuarioId]);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/alunos', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { turma_id } = req.query;
|
||||||
|
try {
|
||||||
|
let query = "SELECT * FROM escola.alunos WHERE usuario_id = $1";
|
||||||
|
let params = [usuarioId];
|
||||||
|
if (turma_id) {
|
||||||
|
query += " AND turma_id = $2";
|
||||||
|
params.push(turma_id);
|
||||||
|
}
|
||||||
|
query += " ORDER BY nome ASC;";
|
||||||
|
const dbRes = await dbPool.query(query, params);
|
||||||
|
res.json(dbRes.rows);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/alunos', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados } = req.body;
|
||||||
|
if (!nome || !turma_id) return res.status(400).json({ error: 'Nome e Turma são obrigatórios' });
|
||||||
|
let nascDate = data_nasc ? data_nasc : null;
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query(
|
||||||
|
"INSERT INTO escola.alunos (usuario_id, turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;",
|
||||||
|
[usuarioId, turma_id, nome, apelido, nascDate, especial, especial_detalhes, pais, autorizados]
|
||||||
|
);
|
||||||
|
res.status(201).json(dbRes.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.put('/api/alunos/:id', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { id } = req.params;
|
||||||
|
const { turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados } = req.body;
|
||||||
|
if (!nome || !turma_id) return res.status(400).json({ error: 'Nome e Turma são obrigatórios' });
|
||||||
|
let nascDate = data_nasc ? data_nasc : null;
|
||||||
|
try {
|
||||||
|
const dbRes = await dbPool.query(
|
||||||
|
"UPDATE escola.alunos SET turma_id = $1, nome = $2, apelido = $3, data_nasc = $4, especial = $5, especial_detalhes = $6, pais = $7, autorizados = $8 WHERE id = $9 AND usuario_id = $10 RETURNING *;",
|
||||||
|
[turma_id, nome, apelido, nascDate, especial, especial_detalhes, pais, autorizados, id, usuarioId]
|
||||||
|
);
|
||||||
|
res.json(dbRes.rows[0]);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/api/alunos/:id', requireAuth, async (req, res) => {
|
||||||
|
const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
|
||||||
|
const { id } = req.params;
|
||||||
|
try {
|
||||||
|
await dbPool.query("DELETE FROM escola.alunos WHERE id = $1 AND usuario_id = $2;", [id, usuarioId]);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(PORT, '0.0.0.0', () => {
|
app.listen(PORT, '0.0.0.0', () => {
|
||||||
console.log(`Servidor rodando na porta ${PORT}`);
|
console.log(`Servidor rodando na porta ${PORT}`);
|
||||||
initDatabase();
|
initDatabase();
|
||||||
|
|||||||
Reference in New Issue
Block a user