Implementação de filtros de Ano Letivo e Turma na modal de emissão e correção da query de crianças únicas para evitar erro de scalar

This commit is contained in:
2026-05-28 13:14:43 +00:00
parent 4beb018680
commit ad67652b7d
3 changed files with 152 additions and 14 deletions
+66 -8
View File
@@ -2672,6 +2672,8 @@ const initApp = () => {
const btnEmitirRelatorio = document.getElementById('btnEmitirRelatorio');
const btnCloseEmitirModal = document.getElementById('btnCloseEmitirModal');
const emitirFilterAno = document.getElementById('emitirFilterAno');
const emitirFilterTurma = document.getElementById('emitirFilterTurma');
const emitirFilterCrianca = document.getElementById('emitirFilterCrianca');
const emitirFilterDataInicio = document.getElementById('emitirFilterDataInicio');
const emitirFilterDataFim = document.getElementById('emitirFilterDataFim');
@@ -2727,22 +2729,57 @@ const initApp = () => {
});
}
// Carregar dados de filtros (crianças e templates)
async function loadFiltersData() {
// Buscar crianças com base nos filtros de Ano e Turma selecionados
async function loadChildrenList() {
try {
const res = await fetch('/api/observacoes/criancas');
const activeAno = emitirFilterAno ? emitirFilterAno.value : '';
const activeTurma = emitirFilterTurma ? emitirFilterTurma.value : '';
const activeCrianca = emitirFilterCrianca ? emitirFilterCrianca.value : '';
let url = '/api/observacoes/criancas';
const params = [];
if (activeAno) params.push(`ano=${activeAno}`);
if (activeTurma) params.push(`turma=${encodeURIComponent(activeTurma)}`);
if (params.length > 0) url += '?' + params.join('&');
const res = await fetch(url);
const criancas = await res.json();
emitirFilterCrianca.innerHTML = '<option value="">Selecione a criança</option>' +
criancas.map(c => `<option value="${c}">${c}</option>`).join('');
(Array.isArray(criancas) ? criancas : []).map(c => `<option value="${c}" ${c === activeCrianca ? 'selected' : ''}>${c}</option>`).join('');
} catch (err) {
console.error('Erro ao buscar crianças para filtros:', err);
console.error('Erro ao carregar lista de crianças:', err);
}
}
// Carregar dados de filtros (anos, turmas, crianças e templates)
async function loadFiltersData() {
try {
const activeAno = emitirFilterAno ? emitirFilterAno.value : '';
const activeTurma = emitirFilterTurma ? emitirFilterTurma.value : '';
const res = await fetch('/api/observacoes/metadata');
const meta = await res.json();
if (emitirFilterAno) {
emitirFilterAno.innerHTML = '<option value="">Todos os anos</option>' +
(meta.anos || []).map(y => `<option value="${y}" ${y === activeAno ? 'selected' : ''}>${y}</option>`).join('');
}
if (emitirFilterTurma) {
emitirFilterTurma.innerHTML = '<option value="">Todas as turmas</option>' +
(meta.turmas || []).map(t => `<option value="${t}" ${t === activeTurma ? 'selected' : ''}>${t}</option>`).join('');
}
} catch (err) {
console.error('Erro ao buscar metadados de filtros:', err);
}
await loadChildrenList();
try {
const activeTemplate = emitirFilterTemplate ? emitirFilterTemplate.value : '';
const res = await fetch('/api/modelos');
const modelos = await res.json();
emitirFilterTemplate.innerHTML = '<option value="">Selecione o modelo</option>' +
modelos.map(m => `<option value="${m.id}">${m.nome}</option>`).join('');
(Array.isArray(modelos) ? modelos : []).map(m => `<option value="${m.id}" ${m.id == activeTemplate ? 'selected' : ''}>${m.nome}</option>`).join('');
} catch (err) {
console.error('Erro ao buscar modelos para filtros:', err);
}
@@ -2750,7 +2787,20 @@ const initApp = () => {
updateObservationsCount();
}
// Ouvintes para atualizar contagem
// Ouvintes para atualizar contagem e filtros de criança
if (emitirFilterAno) {
emitirFilterAno.addEventListener('change', async () => {
await loadChildrenList();
updateObservationsCount();
});
}
if (emitirFilterTurma) {
emitirFilterTurma.addEventListener('change', async () => {
await loadChildrenList();
updateObservationsCount();
});
}
[emitirFilterCrianca, emitirFilterDataInicio, emitirFilterDataFim, emitirFilterTag, emitirFilterTemplate].forEach(el => {
if (el) el.addEventListener('change', updateObservationsCount);
});
@@ -2765,6 +2815,8 @@ const initApp = () => {
const dataFim = emitirFilterDataFim ? emitirFilterDataFim.value : '';
const tag = emitirFilterTag ? emitirFilterTag.value : '';
const modeloId = emitirFilterTemplate ? emitirFilterTemplate.value : '';
const ano = emitirFilterAno ? emitirFilterAno.value : '';
const turma = emitirFilterTurma ? emitirFilterTurma.value : '';
if (!crianca || !modeloId) {
emitirObsCountInfo.textContent = 'Escolha uma criança e um modelo para prosseguir.';
@@ -2779,6 +2831,8 @@ const initApp = () => {
if (dataInicio) url += `&dataInicio=${dataInicio}`;
if (dataFim) url += `&dataFim=${dataFim}`;
if (tag) url += `&tags=${encodeURIComponent(tag)}`;
if (ano) url += `&ano=${ano}`;
if (turma) url += `&turma=${encodeURIComponent(turma)}`;
const res = await fetch(url);
const data = await res.json();
@@ -2808,6 +2862,8 @@ const initApp = () => {
const dataFim = emitirFilterDataFim.value;
const tag = emitirFilterTag.value;
const modeloId = emitirFilterTemplate.value;
const ano = emitirFilterAno ? emitirFilterAno.value : '';
const turma = emitirFilterTurma ? emitirFilterTurma.value : '';
if (!crianca || !modeloId) return;
@@ -2829,7 +2885,9 @@ const initApp = () => {
dataInicio,
dataFim,
tags: tag || undefined,
modeloId
modeloId,
ano,
turma
})
});
+14
View File
@@ -543,6 +543,20 @@
<div class="settings-modal-body" style="padding: 16px; overflow: hidden; display: flex; flex-direction: column; gap: 12px; flex: 1;">
<!-- Filtros e Controles -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; background: var(--bg-secondary); padding: 12px; border-radius: 12px; border: 1px solid var(--border-light);">
<div class="settings-group" style="gap: 4px;">
<label style="color: var(--text-secondary); font-size: 0.78rem; font-weight: 600;">Ano Letivo</label>
<select id="emitirFilterAno" class="obs-select" style="padding: 6px 10px;">
<option value="">Todos os anos</option>
</select>
</div>
<div class="settings-group" style="gap: 4px;">
<label style="color: var(--text-secondary); font-size: 0.78rem; font-weight: 600;">Turma</label>
<select id="emitirFilterTurma" class="obs-select" style="padding: 6px 10px;">
<option value="">Todas as turmas</option>
</select>
</div>
<div class="settings-group" style="gap: 4px;">
<label style="color: var(--text-secondary); font-size: 0.78rem; font-weight: 600;">Criança</label>
<select id="emitirFilterCrianca" class="obs-select" style="padding: 6px 10px;">
+72 -6
View File
@@ -755,13 +755,30 @@ app.delete('/api/modelos/:id', requireAuth, async (req, res) => {
// Obter lista única de crianças já observadas
app.get('/api/observacoes/criancas', requireAuth, async (req, res) => {
const { ano, turma } = req.query;
try {
const dbRes = await dbPool.query(`
let sql = `
SELECT DISTINCT jsonb_array_elements_text(criancas) as nome
FROM escola.observacoes
WHERE criancas IS NOT NULL AND jsonb_array_length(criancas) > 0
ORDER BY nome;
`);
WHERE jsonb_typeof(criancas) = 'array'
`;
const params = [];
let pCount = 1;
if (ano) {
sql += ` AND substring(date from 1 for 4) = $${pCount}`;
params.push(ano);
pCount++;
}
if (turma) {
sql += ` AND turma = $${pCount}`;
params.push(turma);
pCount++;
}
sql += ` ORDER BY nome;`;
const dbRes = await dbPool.query(sql, params);
res.json(dbRes.rows.map(r => r.nome));
} catch (err) {
console.error('Erro ao buscar crianças únicas:', err);
@@ -769,9 +786,34 @@ app.get('/api/observacoes/criancas', requireAuth, async (req, res) => {
}
});
// Obter metadados únicos para filtros (anos letivos e turmas)
app.get('/api/observacoes/metadata', requireAuth, async (req, res) => {
try {
const yearsRes = await dbPool.query(`
SELECT DISTINCT substring(date from 1 for 4) as ano
FROM escola.observacoes
WHERE date IS NOT NULL AND date != ''
ORDER BY ano DESC;
`);
const turmasRes = await dbPool.query(`
SELECT DISTINCT turma
FROM escola.observacoes
WHERE turma IS NOT NULL AND turma != ''
ORDER BY turma;
`);
res.json({
anos: yearsRes.rows.map(r => r.ano).filter(Boolean),
turmas: turmasRes.rows.map(r => r.turma).filter(Boolean)
});
} catch (err) {
console.error('Erro ao buscar metadados de observações:', err);
res.status(500).json({ error: err.message });
}
});
// Contar observações baseadas em filtros
app.get('/api/observacoes/contar', requireAuth, async (req, res) => {
const { crianca, dataInicio, dataFim, tags } = req.query;
const { crianca, dataInicio, dataFim, tags, ano, turma } = req.query;
try {
let sql = `SELECT COUNT(*) FROM escola.observacoes WHERE 1=1`;
const params = [];
@@ -798,6 +840,18 @@ app.get('/api/observacoes/contar', requireAuth, async (req, res) => {
pCount++;
}
if (ano) {
sql += ` AND substring(date from 1 for 4) = $${pCount}`;
params.push(ano);
pCount++;
}
if (turma) {
sql += ` AND turma = $${pCount}`;
params.push(turma);
pCount++;
}
if (tags) {
const tagList = tags.split(',').map(t => t.trim().toLowerCase());
sql += ` AND EXISTS (
@@ -818,7 +872,7 @@ app.get('/api/observacoes/contar', requireAuth, async (req, res) => {
// Gerar relatório consolidado com IA baseado em observações e um modelo
app.post('/api/modelos/gerar-relatorio', requireAuth, async (req, res) => {
const { crianca, dataInicio, dataFim, tags, modeloId } = req.body;
const { crianca, dataInicio, dataFim, tags, modeloId, ano, turma } = req.body;
if (!modeloId) return res.status(400).json({ error: 'O ID do modelo é obrigatório' });
try {
@@ -857,6 +911,18 @@ app.post('/api/modelos/gerar-relatorio', requireAuth, async (req, res) => {
pCount++;
}
if (ano) {
sql += ` AND substring(date from 1 for 4) = $${pCount}`;
params.push(ano);
pCount++;
}
if (turma) {
sql += ` AND turma = $${pCount}`;
params.push(turma);
pCount++;
}
if (tags) {
const tagList = Array.isArray(tags) ? tags : [tags];
sql += ` AND EXISTS (