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