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 emitirFilterDataInicio = document.getElementById('emitirFilterDataInicio');
|
||||
const emitirFilterDataFim = document.getElementById('emitirFilterDataFim');
|
||||
const emitirFilterTag = document.getElementById('emitirFilterTag');
|
||||
const emitirTagsContainer = document.getElementById('emitirTagsContainer');
|
||||
const emitirFilterTemplate = document.getElementById('emitirFilterTemplate');
|
||||
let emitirSelectedTags = [];
|
||||
|
||||
const emitirObsCountInfo = document.getElementById('emitirObsCountInfo');
|
||||
const btnGenerateCompiledReport = document.getElementById('btnGenerateCompiledReport');
|
||||
@@ -2714,7 +2715,11 @@ const initApp = () => {
|
||||
|
||||
emitirFilterDataInicio.value = '';
|
||||
emitirFilterDataFim.value = '';
|
||||
emitirFilterTag.value = '';
|
||||
|
||||
emitirSelectedTags = [];
|
||||
if (emitirTagsContainer) {
|
||||
emitirTagsContainer.querySelectorAll('.tag-pill').forEach(p => p.classList.remove('active'));
|
||||
}
|
||||
|
||||
await loadFiltersData();
|
||||
});
|
||||
@@ -2748,6 +2753,7 @@ const initApp = () => {
|
||||
const res = await fetch(url);
|
||||
const criancas = await res.json();
|
||||
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('');
|
||||
} catch (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 (emitirFilterCrianca) {
|
||||
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
|
||||
async function updateObservationsCount() {
|
||||
const crianca = emitirFilterCrianca ? emitirFilterCrianca.value : '';
|
||||
const dataInicio = emitirFilterDataInicio ? emitirFilterDataInicio.value : '';
|
||||
const dataFim = emitirFilterDataFim ? emitirFilterDataFim.value : '';
|
||||
const tag = emitirFilterTag ? emitirFilterTag.value : '';
|
||||
const tag = emitirSelectedTags.join(',');
|
||||
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.';
|
||||
emitirObsCountInfo.textContent = 'Escolha uma criança (ou "Todas") e um modelo para prosseguir.';
|
||||
btnGenerateCompiledReport.disabled = true;
|
||||
btnGenerateCompiledReport.style.opacity = '0.6';
|
||||
btnGenerateCompiledReport.style.cursor = 'not-allowed';
|
||||
@@ -2863,7 +2886,7 @@ const initApp = () => {
|
||||
const crianca = emitirFilterCrianca.value;
|
||||
const dataInicio = emitirFilterDataInicio.value;
|
||||
const dataFim = emitirFilterDataFim.value;
|
||||
const tag = emitirFilterTag.value;
|
||||
const tags = emitirSelectedTags;
|
||||
const modeloId = emitirFilterTemplate.value;
|
||||
const ano = emitirFilterAno ? emitirFilterAno.value : '';
|
||||
const turma = emitirFilterTurma ? emitirFilterTurma.value : '';
|
||||
@@ -2887,7 +2910,7 @@ const initApp = () => {
|
||||
crianca,
|
||||
dataInicio,
|
||||
dataFim,
|
||||
tags: tag || undefined,
|
||||
tags: tags.length > 0 ? tags : undefined,
|
||||
modeloId,
|
||||
ano,
|
||||
turma
|
||||
|
||||
+11
-12
@@ -574,18 +574,17 @@
|
||||
<input type="date" id="emitirFilterDataFim" class="obs-input" style="padding: 5px 8px;">
|
||||
</div>
|
||||
|
||||
<div class="settings-group" style="gap: 4px;">
|
||||
<label style="color: var(--text-secondary); font-size: 0.78rem; font-weight: 600;">Tags</label>
|
||||
<select id="emitirFilterTag" class="obs-select" style="padding: 6px 10px;">
|
||||
<option value="">Todas as tags</option>
|
||||
<option value="social">Social</option>
|
||||
<option value="cognitivo">Cognitivo</option>
|
||||
<option value="motor">Motor</option>
|
||||
<option value="linguagem">Linguagem</option>
|
||||
<option value="emocional">Emocional</option>
|
||||
<option value="colaborativo">Colaborativo</option>
|
||||
<option value="autônomo">Autônomo</option>
|
||||
</select>
|
||||
<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 (Múltipla Seleção)</label>
|
||||
<div id="emitirTagsContainer" style="display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0;">
|
||||
<button type="button" class="tag-pill" data-tag="social">Social</button>
|
||||
<button type="button" class="tag-pill" data-tag="cognitivo">Cognitivo</button>
|
||||
<button type="button" class="tag-pill" data-tag="motor">Motor</button>
|
||||
<button type="button" class="tag-pill" data-tag="linguagem">Linguagem</button>
|
||||
<button type="button" class="tag-pill" data-tag="emocional">Emocional</button>
|
||||
<button type="button" class="tag-pill" data-tag="colaborativo">Colaborativo</button>
|
||||
<button type="button" class="tag-pill" data-tag="autônomo">Autônomo</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group" style="gap: 4px;">
|
||||
|
||||
@@ -3012,3 +3012,29 @@ code {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
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 = [];
|
||||
let pCount = 1;
|
||||
|
||||
if (crianca) {
|
||||
if (crianca && crianca !== 'todas') {
|
||||
sql += ` AND jsonb_typeof(criancas) = 'array' AND EXISTS (
|
||||
SELECT 1 FROM jsonb_array_elements_text(criancas) AS c
|
||||
WHERE LOWER(c) LIKE $${pCount}
|
||||
@@ -890,7 +890,7 @@ app.post('/api/modelos/gerar-relatorio', requireAuth, async (req, res) => {
|
||||
const params = [];
|
||||
let pCount = 1;
|
||||
|
||||
if (crianca) {
|
||||
if (crianca && crianca !== 'todas') {
|
||||
sql += ` AND jsonb_typeof(criancas) = 'array' AND EXISTS (
|
||||
SELECT 1 FROM jsonb_array_elements_text(criancas) AS c
|
||||
WHERE LOWER(c) LIKE $${pCount}
|
||||
@@ -951,8 +951,9 @@ app.post('/api/modelos/gerar-relatorio', requireAuth, async (req, res) => {
|
||||
const agentConfig = readAgentConfig();
|
||||
const agentName = agentConfig.agentName || "Kemily";
|
||||
|
||||
const isGlobal = crianca === 'todas';
|
||||
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.
|
||||
|
||||
@@ -1058,7 +1059,7 @@ Orientações adicionais:
|
||||
res.json({
|
||||
report: generatedReport,
|
||||
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'),
|
||||
totalObservacoes: observations.length
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user