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 btnEmitirRelatorio = document.getElementById('btnEmitirRelatorio');
|
||||||
const btnCloseEmitirModal = document.getElementById('btnCloseEmitirModal');
|
const btnCloseEmitirModal = document.getElementById('btnCloseEmitirModal');
|
||||||
|
|
||||||
|
const emitirFilterAno = document.getElementById('emitirFilterAno');
|
||||||
|
const emitirFilterTurma = document.getElementById('emitirFilterTurma');
|
||||||
const emitirFilterCrianca = document.getElementById('emitirFilterCrianca');
|
const emitirFilterCrianca = document.getElementById('emitirFilterCrianca');
|
||||||
const emitirFilterDataInicio = document.getElementById('emitirFilterDataInicio');
|
const emitirFilterDataInicio = document.getElementById('emitirFilterDataInicio');
|
||||||
const emitirFilterDataFim = document.getElementById('emitirFilterDataFim');
|
const emitirFilterDataFim = document.getElementById('emitirFilterDataFim');
|
||||||
@@ -2727,22 +2729,57 @@ const initApp = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carregar dados de filtros (crianças e templates)
|
// Buscar crianças com base nos filtros de Ano e Turma selecionados
|
||||||
async function loadFiltersData() {
|
async function loadChildrenList() {
|
||||||
try {
|
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();
|
const criancas = await res.json();
|
||||||
emitirFilterCrianca.innerHTML = '<option value="">Selecione a criança</option>' +
|
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) {
|
} 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 {
|
try {
|
||||||
|
const activeTemplate = emitirFilterTemplate ? emitirFilterTemplate.value : '';
|
||||||
const res = await fetch('/api/modelos');
|
const res = await fetch('/api/modelos');
|
||||||
const modelos = await res.json();
|
const modelos = await res.json();
|
||||||
emitirFilterTemplate.innerHTML = '<option value="">Selecione o modelo</option>' +
|
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) {
|
} catch (err) {
|
||||||
console.error('Erro ao buscar modelos para filtros:', err);
|
console.error('Erro ao buscar modelos para filtros:', err);
|
||||||
}
|
}
|
||||||
@@ -2750,7 +2787,20 @@ const initApp = () => {
|
|||||||
updateObservationsCount();
|
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 => {
|
[emitirFilterCrianca, emitirFilterDataInicio, emitirFilterDataFim, emitirFilterTag, emitirFilterTemplate].forEach(el => {
|
||||||
if (el) el.addEventListener('change', updateObservationsCount);
|
if (el) el.addEventListener('change', updateObservationsCount);
|
||||||
});
|
});
|
||||||
@@ -2765,6 +2815,8 @@ const initApp = () => {
|
|||||||
const dataFim = emitirFilterDataFim ? emitirFilterDataFim.value : '';
|
const dataFim = emitirFilterDataFim ? emitirFilterDataFim.value : '';
|
||||||
const tag = emitirFilterTag ? emitirFilterTag.value : '';
|
const tag = emitirFilterTag ? emitirFilterTag.value : '';
|
||||||
const modeloId = emitirFilterTemplate ? emitirFilterTemplate.value : '';
|
const modeloId = emitirFilterTemplate ? emitirFilterTemplate.value : '';
|
||||||
|
const ano = emitirFilterAno ? emitirFilterAno.value : '';
|
||||||
|
const turma = emitirFilterTurma ? emitirFilterTurma.value : '';
|
||||||
|
|
||||||
if (!crianca || !modeloId) {
|
if (!crianca || !modeloId) {
|
||||||
emitirObsCountInfo.textContent = 'Escolha uma criança e um modelo para prosseguir.';
|
emitirObsCountInfo.textContent = 'Escolha uma criança e um modelo para prosseguir.';
|
||||||
@@ -2779,6 +2831,8 @@ const initApp = () => {
|
|||||||
if (dataInicio) url += `&dataInicio=${dataInicio}`;
|
if (dataInicio) url += `&dataInicio=${dataInicio}`;
|
||||||
if (dataFim) url += `&dataFim=${dataFim}`;
|
if (dataFim) url += `&dataFim=${dataFim}`;
|
||||||
if (tag) url += `&tags=${encodeURIComponent(tag)}`;
|
if (tag) url += `&tags=${encodeURIComponent(tag)}`;
|
||||||
|
if (ano) url += `&ano=${ano}`;
|
||||||
|
if (turma) url += `&turma=${encodeURIComponent(turma)}`;
|
||||||
|
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -2808,6 +2862,8 @@ const initApp = () => {
|
|||||||
const dataFim = emitirFilterDataFim.value;
|
const dataFim = emitirFilterDataFim.value;
|
||||||
const tag = emitirFilterTag.value;
|
const tag = emitirFilterTag.value;
|
||||||
const modeloId = emitirFilterTemplate.value;
|
const modeloId = emitirFilterTemplate.value;
|
||||||
|
const ano = emitirFilterAno ? emitirFilterAno.value : '';
|
||||||
|
const turma = emitirFilterTurma ? emitirFilterTurma.value : '';
|
||||||
|
|
||||||
if (!crianca || !modeloId) return;
|
if (!crianca || !modeloId) return;
|
||||||
|
|
||||||
@@ -2829,7 +2885,9 @@ const initApp = () => {
|
|||||||
dataInicio,
|
dataInicio,
|
||||||
dataFim,
|
dataFim,
|
||||||
tags: tag || undefined,
|
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;">
|
<div class="settings-modal-body" style="padding: 16px; overflow: hidden; display: flex; flex-direction: column; gap: 12px; flex: 1;">
|
||||||
<!-- Filtros e Controles -->
|
<!-- 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 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;">
|
<div class="settings-group" style="gap: 4px;">
|
||||||
<label style="color: var(--text-secondary); font-size: 0.78rem; font-weight: 600;">Criança</label>
|
<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;">
|
<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
|
// Obter lista única de crianças já observadas
|
||||||
app.get('/api/observacoes/criancas', requireAuth, async (req, res) => {
|
app.get('/api/observacoes/criancas', requireAuth, async (req, res) => {
|
||||||
|
const { ano, turma } = req.query;
|
||||||
try {
|
try {
|
||||||
const dbRes = await dbPool.query(`
|
let sql = `
|
||||||
SELECT DISTINCT jsonb_array_elements_text(criancas) as nome
|
SELECT DISTINCT jsonb_array_elements_text(criancas) as nome
|
||||||
FROM escola.observacoes
|
FROM escola.observacoes
|
||||||
WHERE criancas IS NOT NULL AND jsonb_array_length(criancas) > 0
|
WHERE jsonb_typeof(criancas) = 'array'
|
||||||
ORDER BY nome;
|
`;
|
||||||
`);
|
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));
|
res.json(dbRes.rows.map(r => r.nome));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro ao buscar crianças únicas:', 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
|
// Contar observações baseadas em filtros
|
||||||
app.get('/api/observacoes/contar', requireAuth, async (req, res) => {
|
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 {
|
try {
|
||||||
let sql = `SELECT COUNT(*) FROM escola.observacoes WHERE 1=1`;
|
let sql = `SELECT COUNT(*) FROM escola.observacoes WHERE 1=1`;
|
||||||
const params = [];
|
const params = [];
|
||||||
@@ -798,6 +840,18 @@ app.get('/api/observacoes/contar', requireAuth, async (req, res) => {
|
|||||||
pCount++;
|
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) {
|
if (tags) {
|
||||||
const tagList = tags.split(',').map(t => t.trim().toLowerCase());
|
const tagList = tags.split(',').map(t => t.trim().toLowerCase());
|
||||||
sql += ` AND EXISTS (
|
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
|
// Gerar relatório consolidado com IA baseado em observações e um modelo
|
||||||
app.post('/api/modelos/gerar-relatorio', requireAuth, async (req, res) => {
|
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' });
|
if (!modeloId) return res.status(400).json({ error: 'O ID do modelo é obrigatório' });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -857,6 +911,18 @@ app.post('/api/modelos/gerar-relatorio', requireAuth, async (req, res) => {
|
|||||||
pCount++;
|
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) {
|
if (tags) {
|
||||||
const tagList = Array.isArray(tags) ? tags : [tags];
|
const tagList = Array.isArray(tags) ? tags : [tags];
|
||||||
sql += ` AND EXISTS (
|
sql += ` AND EXISTS (
|
||||||
|
|||||||
Reference in New Issue
Block a user