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:
+66
-8
@@ -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
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -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;">
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user