Implementa múltipla seleção de tags via pills e suporte a Relatório Global com a opção todas as crianças

This commit is contained in:
2026-05-28 16:41:44 +00:00
parent 9fde9251f4
commit 999ca2e448
4 changed files with 72 additions and 23 deletions
+30 -7
View File
@@ -2680,8 +2680,9 @@ const initApp = () => {
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');
const emitirFilterTag = document.getElementById('emitirFilterTag'); const emitirTagsContainer = document.getElementById('emitirTagsContainer');
const emitirFilterTemplate = document.getElementById('emitirFilterTemplate'); const emitirFilterTemplate = document.getElementById('emitirFilterTemplate');
let emitirSelectedTags = [];
const emitirObsCountInfo = document.getElementById('emitirObsCountInfo'); const emitirObsCountInfo = document.getElementById('emitirObsCountInfo');
const btnGenerateCompiledReport = document.getElementById('btnGenerateCompiledReport'); const btnGenerateCompiledReport = document.getElementById('btnGenerateCompiledReport');
@@ -2714,7 +2715,11 @@ const initApp = () => {
emitirFilterDataInicio.value = ''; emitirFilterDataInicio.value = '';
emitirFilterDataFim.value = ''; emitirFilterDataFim.value = '';
emitirFilterTag.value = '';
emitirSelectedTags = [];
if (emitirTagsContainer) {
emitirTagsContainer.querySelectorAll('.tag-pill').forEach(p => p.classList.remove('active'));
}
await loadFiltersData(); await loadFiltersData();
}); });
@@ -2748,6 +2753,7 @@ const initApp = () => {
const res = await fetch(url); 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>' +
`<option value="todas" ${activeCrianca === 'todas' ? 'selected' : ''}>Todas as crianças (Relatório Global)</option>` +
(Array.isArray(criancas) ? criancas : []).map(c => `<option value="${c}" ${c === activeCrianca ? 'selected' : ''}>${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 carregar lista de crianças:', err); console.error('Erro ao carregar lista de crianças:', err);
@@ -2804,25 +2810,42 @@ const initApp = () => {
}); });
} }
[emitirFilterCrianca, emitirFilterDataInicio, emitirFilterDataFim, emitirFilterTag, emitirFilterTemplate].forEach(el => { [emitirFilterCrianca, emitirFilterDataInicio, emitirFilterDataFim, emitirFilterTemplate].forEach(el => {
if (el) el.addEventListener('change', updateObservationsCount); if (el) el.addEventListener('change', updateObservationsCount);
}); });
if (emitirFilterCrianca) { if (emitirFilterCrianca) {
emitirFilterCrianca.addEventListener('input', updateObservationsCount); emitirFilterCrianca.addEventListener('input', updateObservationsCount);
} }
// Adicionar ouvintes para as pills de tags
if (emitirTagsContainer) {
emitirTagsContainer.querySelectorAll('.tag-pill').forEach(pill => {
pill.addEventListener('click', () => {
const tag = pill.getAttribute('data-tag');
if (pill.classList.contains('active')) {
pill.classList.remove('active');
emitirSelectedTags = emitirSelectedTags.filter(t => t !== tag);
} else {
pill.classList.add('active');
emitirSelectedTags.push(tag);
}
updateObservationsCount();
});
});
}
// Recalcular quantidade de observações elegíveis // Recalcular quantidade de observações elegíveis
async function updateObservationsCount() { async function updateObservationsCount() {
const crianca = emitirFilterCrianca ? emitirFilterCrianca.value : ''; const crianca = emitirFilterCrianca ? emitirFilterCrianca.value : '';
const dataInicio = emitirFilterDataInicio ? emitirFilterDataInicio.value : ''; const dataInicio = emitirFilterDataInicio ? emitirFilterDataInicio.value : '';
const dataFim = emitirFilterDataFim ? emitirFilterDataFim.value : ''; const dataFim = emitirFilterDataFim ? emitirFilterDataFim.value : '';
const tag = emitirFilterTag ? emitirFilterTag.value : ''; const tag = emitirSelectedTags.join(',');
const modeloId = emitirFilterTemplate ? emitirFilterTemplate.value : ''; const modeloId = emitirFilterTemplate ? emitirFilterTemplate.value : '';
const ano = emitirFilterAno ? emitirFilterAno.value : ''; const ano = emitirFilterAno ? emitirFilterAno.value : '';
const turma = emitirFilterTurma ? emitirFilterTurma.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 (ou "Todas") e um modelo para prosseguir.';
btnGenerateCompiledReport.disabled = true; btnGenerateCompiledReport.disabled = true;
btnGenerateCompiledReport.style.opacity = '0.6'; btnGenerateCompiledReport.style.opacity = '0.6';
btnGenerateCompiledReport.style.cursor = 'not-allowed'; btnGenerateCompiledReport.style.cursor = 'not-allowed';
@@ -2863,7 +2886,7 @@ const initApp = () => {
const crianca = emitirFilterCrianca.value; const crianca = emitirFilterCrianca.value;
const dataInicio = emitirFilterDataInicio.value; const dataInicio = emitirFilterDataInicio.value;
const dataFim = emitirFilterDataFim.value; const dataFim = emitirFilterDataFim.value;
const tag = emitirFilterTag.value; const tags = emitirSelectedTags;
const modeloId = emitirFilterTemplate.value; const modeloId = emitirFilterTemplate.value;
const ano = emitirFilterAno ? emitirFilterAno.value : ''; const ano = emitirFilterAno ? emitirFilterAno.value : '';
const turma = emitirFilterTurma ? emitirFilterTurma.value : ''; const turma = emitirFilterTurma ? emitirFilterTurma.value : '';
@@ -2887,7 +2910,7 @@ const initApp = () => {
crianca, crianca,
dataInicio, dataInicio,
dataFim, dataFim,
tags: tag || undefined, tags: tags.length > 0 ? tags : undefined,
modeloId, modeloId,
ano, ano,
turma turma
+11 -12
View File
@@ -574,18 +574,17 @@
<input type="date" id="emitirFilterDataFim" class="obs-input" style="padding: 5px 8px;"> <input type="date" id="emitirFilterDataFim" class="obs-input" style="padding: 5px 8px;">
</div> </div>
<div class="settings-group" style="gap: 4px;"> <div class="settings-group" style="gap: 6px; grid-column: span 2;">
<label style="color: var(--text-secondary); font-size: 0.78rem; font-weight: 600;">Tags</label> <label style="color: var(--text-secondary); font-size: 0.78rem; font-weight: 600;">Tags (Múltipla Seleção)</label>
<select id="emitirFilterTag" class="obs-select" style="padding: 6px 10px;"> <div id="emitirTagsContainer" style="display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0;">
<option value="">Todas as tags</option> <button type="button" class="tag-pill" data-tag="social">Social</button>
<option value="social">Social</option> <button type="button" class="tag-pill" data-tag="cognitivo">Cognitivo</button>
<option value="cognitivo">Cognitivo</option> <button type="button" class="tag-pill" data-tag="motor">Motor</button>
<option value="motor">Motor</option> <button type="button" class="tag-pill" data-tag="linguagem">Linguagem</button>
<option value="linguagem">Linguagem</option> <button type="button" class="tag-pill" data-tag="emocional">Emocional</button>
<option value="emocional">Emocional</option> <button type="button" class="tag-pill" data-tag="colaborativo">Colaborativo</button>
<option value="colaborativo">Colaborativo</option> <button type="button" class="tag-pill" data-tag="autônomo">Autônomo</button>
<option value="autônomo">Autônomo</option> </div>
</select>
</div> </div>
<div class="settings-group" style="gap: 4px;"> <div class="settings-group" style="gap: 4px;">
+26
View File
@@ -3012,3 +3012,29 @@ code {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
transform: scale(1.05); transform: scale(1.05);
} }
/* Estilo para pills interativas de tags */
.tag-pill {
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-light);
color: var(--text-secondary, #a3a3a3);
padding: 6px 14px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
user-select: none;
}
.tag-pill:hover {
background: rgba(255, 255, 255, 0.1);
color: var(--text-primary, #ffffff);
}
.tag-pill.active {
background: rgba(16, 163, 127, 0.2);
border-color: var(--brand-green, #10a37f);
color: var(--brand-green, #10a37f);
font-weight: 600;
}
+5 -4
View File
@@ -819,7 +819,7 @@ app.get('/api/observacoes/contar', requireAuth, async (req, res) => {
const params = []; const params = [];
let pCount = 1; let pCount = 1;
if (crianca) { if (crianca && crianca !== 'todas') {
sql += ` AND jsonb_typeof(criancas) = 'array' AND EXISTS ( sql += ` AND jsonb_typeof(criancas) = 'array' AND EXISTS (
SELECT 1 FROM jsonb_array_elements_text(criancas) AS c SELECT 1 FROM jsonb_array_elements_text(criancas) AS c
WHERE LOWER(c) LIKE $${pCount} WHERE LOWER(c) LIKE $${pCount}
@@ -890,7 +890,7 @@ app.post('/api/modelos/gerar-relatorio', requireAuth, async (req, res) => {
const params = []; const params = [];
let pCount = 1; let pCount = 1;
if (crianca) { if (crianca && crianca !== 'todas') {
sql += ` AND jsonb_typeof(criancas) = 'array' AND EXISTS ( sql += ` AND jsonb_typeof(criancas) = 'array' AND EXISTS (
SELECT 1 FROM jsonb_array_elements_text(criancas) AS c SELECT 1 FROM jsonb_array_elements_text(criancas) AS c
WHERE LOWER(c) LIKE $${pCount} WHERE LOWER(c) LIKE $${pCount}
@@ -951,8 +951,9 @@ app.post('/api/modelos/gerar-relatorio', requireAuth, async (req, res) => {
const agentConfig = readAgentConfig(); const agentConfig = readAgentConfig();
const agentName = agentConfig.agentName || "Kemily"; const agentName = agentConfig.agentName || "Kemily";
const isGlobal = crianca === 'todas';
const systemPrompt = `Você é a ${agentName}, uma assistente pedagógica e especialista em educação infantil. const systemPrompt = `Você é a ${agentName}, uma assistente pedagógica e especialista em educação infantil.
Seu objetivo é analisar o histórico de observações de uma criança e redigir um relatório pedagógico consolidado e profissional. Seu objetivo é analisar o histórico de observações ${isGlobal ? 'da turma' : 'de uma criança'} e redigir um relatório pedagógico consolidado e profissional ${isGlobal ? 'globalizado da sala de aula / turma' : 'sobre o desenvolvimento individual desta criança'}.
Você DEVE estruturar o relatório seguindo exatamente as seções e diretrizes fornecidas no modelo de relatório selecionado. Você DEVE estruturar o relatório seguindo exatamente as seções e diretrizes fornecidas no modelo de relatório selecionado.
@@ -1058,7 +1059,7 @@ Orientações adicionais:
res.json({ res.json({
report: generatedReport, report: generatedReport,
templateNome: template.nome, templateNome: template.nome,
crianca: crianca || 'Todos os alunos', crianca: crianca === 'todas' ? 'Sala de Aula (Relatório Global)' : (crianca || 'Todos os alunos'),
periodo: (dataInicio || '') + (dataInicio && dataFim ? ' a ' : '') + (dataFim || 'Todo o período'), periodo: (dataInicio || '') + (dataInicio && dataFim ? ' a ' : '') + (dataFim || 'Todo o período'),
totalObservacoes: observations.length totalObservacoes: observations.length
}); });