alterações gerais

This commit is contained in:
2025-06-16 13:11:57 -04:00
parent 9dca0d6022
commit f196773705
45 changed files with 6774 additions and 222 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,76 @@
.bg-light-gradient {
background: linear-gradient(to right, #f8f9fa, #e9ecef);
}
.search-card {
border-radius: 15px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
border: none;
}
.result-card {
transition: transform 0.2s, box-shadow 0.2s;
border-radius: 10px;
}
.result-card:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
}
.page-item.active .page-link {
background-color: #0d6efd;
border-color: #0d6efd;
}
.date-picker {
background-color: #fff;
border: 1px solid #ced4da;
border-radius: 4px;
padding: 0.375rem 0.75rem;
}
.best-match {
border-left: 4px solid #0d6efd;
background-color: rgba(13, 110, 253, 0.05);
}
.page-content {
max-height: 400px;
overflow-y: auto;
font-size: 0.9rem;
}
.accordion-button:not(.collapsed) {
background-color: rgba(13, 110, 253, 0.1);
color: #0d6efd;
}
.accordion-button:focus {
box-shadow: none;
}
mark {
background-color: #fff3cd;
padding: 0.1em 0.2em;
border-radius: 2px;
}
.page-badge {
background-color: #6c757d;
color: white;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.75rem;
}
.match-score {
color: #0d6efd;
font-size: 0.85rem;
font-weight: 500;
}
.btn-sort {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
.btn-sort.active {
background-color: #0d6efd;
color: white;
}
.search-options {
display: flex;
gap: 10px;
}
@media (max-width: 768px) {
.search-options {
flex-direction: column;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
const API_BASE_URL = "http://109.199.98.226";

View File

@ -0,0 +1,242 @@
document.addEventListener('alpine:init', () => {
Alpine.data('searchApp', () => ({
searchParams: {
q: '',
numero_diario: '',
data_inicio: '',
data_fim: '',
modo_busca: 'exata',
ordenar_por: 'data_asc',
page: 1,
page_size: 10
},
searchResults: null,
isLoading: false,
hasSearched: false,
error: null,
showAdvanced: false,
expandedContents: {},
suggestion: null,
ultimoTermoBuscado: '',
get shouldShowSuggestion() {
if (!this.suggestion || !this.searchParams.q) return false;
// Função para remover acentos e converter para minúsculas
const normalize = (text) => {
return text.toLowerCase()
.normalize('NFD')
.replace("/", " ")
.replace(/[^a-z0-9\s]/g, " ") // Substitui todos os outros símbolos por espaço
.replace(/[\u0300-\u036f]/g, '');
};
const normalizedQuery = normalize(this.searchParams.q);
const normalizedSuggestion = normalize(this.suggestion);
// Só mostra a sugestão se for diferente do termo buscado (ignorando acentos e caixa)
return normalizedQuery !== normalizedSuggestion;
},
// Usa a sugestão como novo termo de busca
usesuggestion() {
this.searchParams.q = this.suggestion;
this.searchParams.page = 1;
this.performSearch();
},
// Verifica se um diário tem uma melhor correspondência
hasBestMatch(diario) {
return diario.paginas && diario.paginas.length > 0;
},
// Muda a ordenação e faz uma nova busca
changeOrder(order) {
if (this.searchParams.ordenar_por !== order) {
this.searchParams.ordenar_por = order;
this.searchParams.page = 1; // Volta para a primeira página
this.performSearch();
}
},
// Obter sugestão da API
async getSuggestion(query) {
if (!query) return null;
try {
const url = new URL('http://192.168.235.234/api/v1/diarios/sugestao');
url.searchParams.append('q', query);
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
return data.sugestao;
}
return null;
} catch (error) {
console.error('Erro ao buscar sugestão:', error);
return null;
}
},
get totalPages() {
if (!this.searchResults) return 0;
return Math.ceil(this.searchResults.total / this.searchResults.por_pagina);
},
get paginationArray() {
const pages = [];
const currentPage = this.searchParams.page;
const totalPages = this.totalPages;
// Função auxiliar para adicionar páginas
const addPage = (page) => {
if (page >= 1 && page <= totalPages && !pages.includes(page)) {
pages.push(page);
}
};
// Sempre mostrar primeira página, página atual, última página
// e 1-2 páginas adjacentes à página atual
addPage(1);
addPage(currentPage - 2);
addPage(currentPage - 1);
addPage(currentPage);
addPage(currentPage + 1);
addPage(currentPage + 2);
addPage(totalPages);
// Ordenar e adicionar separadores
const result = pages.sort((a, b) => a - b);
return result;
},
// Retorna a melhor página (maior score) de um diário
getBestPage(paginas) {
if (!paginas || paginas.length === 0) return null;
// Ordena as páginas por score (se disponível) ou pelo número da página se não houver score
const sortedPages = [...paginas].sort((a, b) => {
if (a.score === undefined || b.score === undefined) return 0;
if (a.score === undefined) return 1;
if (b.score === undefined) return -1;
return b.score - a.score;
});
return sortedPages[0];
},
// Retorna todas as páginas exceto a melhor
getOtherPages(paginas) {
if (!paginas || paginas.length <= 1) return [];
const bestPage = this.getBestPage(paginas);
if (!bestPage) return paginas;
return paginas.filter(p => p.numero !== bestPage.numero)
.sort((a, b) => a.numero - b.numero); // Ordena por número da página
},
// Controla a exibição do conteúdo completo de uma página
toggleFullContent(diarioIndex, paginaNumero) {
const key = `${diarioIndex}-${paginaNumero}`;
this.expandedContents[key] = !this.expandedContents[key];
},
// Verifica se um conteúdo está expandido
isFullContentVisible(diarioIndex, paginaNumero) {
const key = `${diarioIndex}-${paginaNumero}`;
return this.expandedContents[key] === true;
},
async performSearch() {
this.isLoading = true;
this.error = null;
this.hasSearched = true;
this.expandedContents = {}; // Resetar estados expandidos
this.suggestion = null; // Resetar a sugestão
try {
let suggestionPromise = null;
if (this.searchParams.q) {
suggestionPromise = this.getSuggestion(this.searchParams.q);
}
if (this.searchParams.q !== this.ultimoTermoBuscado) {
this.searchParams.page = 1;
}
this.ultimoTermoBuscado = this.searchParams.q;
// Usando agora o endpoint busca
const url = new URL('http://192.168.235.234/api/v1/diarios/busca');
// Adicionar parâmetros à URL
Object.entries(this.searchParams).forEach(([key, value]) => {
if (value !== '' && value !== null) {
url.searchParams.append(key, value);
}
});
const response = await fetch(url);
if (!response.ok) {
const errorData = await response.json().catch(() => null);
throw new Error(errorData?.message || `Erro HTTP: ${response.status}`);
}
this.searchResults = await response.json();
// Processar os resultados para garantir que as páginas tenham score
if (this.searchResults && this.searchResults.resultados) {
this.searchResults.resultados.forEach(diario => {
if (diario.paginas) {
// Atribuir scores padrão se não existirem
diario.paginas.forEach((pagina, index) => {
if (pagina.score === undefined || pagina.score === null) {
pagina.score = diario.paginas.length - index; // Score inversamente proporcional ao índice
}
});
}
});
}
if (suggestionPromise) {
this.suggestion = await suggestionPromise;
}
} catch (error) {
console.error('Erro na busca:', error);
this.error = `Erro ao buscar diários: ${error.message}`;
} finally {
this.isLoading = false;
}
},
formatDate(dateString) {
const options = { day: '2-digit', month: '2-digit', year: 'numeric' };
return new Date(dateString + 'T00:00:00').toLocaleDateString('pt-BR', options);
},
goToPage(page) {
if (page < 1 || page > this.totalPages) return;
this.searchParams.page = page;
this.performSearch();
window.scrollTo({ top: 0, behavior: 'smooth' });
},
resetSearch() {
this.searchParams = {
q: '',
numero_diario: '',
data_inicio: '',
data_fim: '',
modo_busca: 'exata',
ordenar_por: 'relevancia',
page: 1,
page_size: 10
};
this.searchResults = null;
this.hasSearched = false;
this.error = null;
this.expandedContents = {};
}
}));
});

View File

@ -90,17 +90,6 @@
{# URL provided by django-allauth/account/urls.py #}
<a class="nav-link" href="{% url 'account_logout' %}">{% translate "Sign Out" %}</a>
</li>
{% else %}
{% if ACCOUNT_ALLOW_REGISTRATION %}
<li class="nav-item">
{# URL provided by django-allauth/account/urls.py #}
<a id="sign-up-link" class="nav-link" href="{% url 'account_signup' %}">{% translate "Sign Up" %}</a>
</li>
{% endif %}
<li class="nav-item">
{# URL provided by django-allauth/account/urls.py #}
<a id="log-in-link" class="nav-link" href="{% url 'account_login' %}">{% translate "Sign In" %}</a>
</li>
{% endif %}
</ul>
</div>

View File

@ -1,25 +1,40 @@
{% load static %}
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sistema de Busca de Diários Oficiais</title>
<title>Diários Oficiais</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="{% static 'css/bootstrap-custom.min.css' %}" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link rel="stylesheet" href="{% static 'css/bootstrap-icons.css' %}">
<!-- Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.1/dist/cdn.min.js"></script>
<script defer src="{% static 'js/alpine.min.js' %}"></script>
<!-- Estilos customizados -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
<link rel="icon" href="{% static 'images/favicon.ico' %}" type="image/x-icon">
</head>
<body class="bg-light-gradient">
<div class="container py-5" x-data="searchApp">
<div class="container py-5" x-data="searchApp"> <!-- Container do Logo e Imagem Direita -->
<div class="row justify-content-center mb-4">
<div class="col-12 col-lg-10">
<div class="d-flex justify-content-between align-items-center mb-5">
<!-- Logo à esquerda -->
<div class="logo-container">
<img src="{% static 'images/logo.jpg' %}" alt="Logo" class="img-fluid">
</div>
<!-- Imagem à direita -->
<div class="risco-container">
<img src="{% static 'images/risco.jpg' %}" alt="Risco" class="img-fluid">
</div>
</div>
</div>
<div class="row justify-content-center mb-4">
<div class="col-12 col-lg-10">
<div class="text-center mb-5">
<h1 class="display-5 fw-bold text-primary mb-3">
<i class="bi bi-search me-2"></i>Sistema de Busca de Diários Oficiais
<i class="bi bi-search me-2"></i>Diários Oficiais
</h1>
</div>
@ -114,7 +129,7 @@
</form>
</div>
</div>
<!-- Adicionando a seção de "Você quis dizer" após iniciar a busca -->
<!-- Seção Você quis dizer -->
<template x-if="!isLoading && !error && searchResults && suggestion && shouldShowSuggestion">
<div class="mb-3 mt-3 alert alert-info d-flex align-items-center">
<i class="bi bi-lightbulb-fill me-2"></i>
@ -283,8 +298,7 @@
<div class="container">
<div class="row">
<div class="col-md-6">
<h5>Sistema de Busca de Diários Oficiais</h5>
<p class="small">Uma ferramenta avançada para pesquisa em diários oficiais.</p>
<h5>Diários Oficiais</h5>
</div>
<div class="col-md-6 text-md-end">
<p class="small mb-0">&copy; 2025 Todos os direitos reservados</p>
@ -292,10 +306,88 @@
</div>
</div>
</footer>
<!-- Botão de ajuda que abre o modal -->
<button type="button" class="btn btn-outline-secondary position-fixed bottom-0 end-0 m-3" data-bs-toggle="modal" data-bs-target="#helpModal">
<i class="bi bi-question-circle"></i> Ajuda
</button>
<!-- Modal de Ajuda -->
<div class="modal fade" id="helpModal" tabindex="-1" aria-labelledby="helpModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="helpModalLabel">Ajuda - Sistema de Busca de Diários Oficiais</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Fechar"></button>
</div>
<div class="modal-body">
<div class="mb-4">
<h6>Atenção quanto à qualidade dos dados</h6>
<p>Alguns documentos podem conter erros de leitura devido à baixa qualidade das imagens dos PDFs originais. Isso pode afetar a precisão da busca, especialmente no modo de <strong>busca exata</strong>.</p>
<p>Nesses casos, recomendamos utilizar o modo <strong>qualquer termo</strong>, que é mais tolerante a pequenas falhas de reconhecimento de texto.</p>
</div>
<h5>Como realizar buscas</h5>
<hr>
<div class="mb-4">
<h6>Busca básica</h6>
<p>Digite o termo que deseja buscar no campo principal e clique em "Buscar". O sistema irá localizar ocorrências desse termo nos Diários Oficiais.</p>
<ul>
<li><strong>Número do Diário:</strong> Se souber o número específico do diário, digite-o neste campo para filtrar os resultados.</li>
<li><strong>Modo de Busca:</strong>
<ul>
<li><em>Busca exata</em> - Encontra apenas documentos que contenham exatamente o termo informado.</li>
<li><em>Qualquer termo</em> - Encontra documentos que contenham qualquer um dos termos informados.</li>
</ul>
</li>
</ul>
</div>
<div class="mb-4">
<h6>Ordenação dos resultados</h6>
<p>Você pode ordenar os resultados de três formas:</p>
<ul>
<li><strong>Relevância:</strong> Mostra primeiro os documentos mais relevantes para sua busca.</li>
<li><strong>Data (Decrescente):</strong> Mostra os diários mais recentes primeiro.</li>
<li><strong>Data (Crescente):</strong> Mostra os diários mais antigos primeiro.</li>
</ul>
</div>
<div class="mb-4">
<h6>Filtros avançados</h6>
<p>Clique em "Mostrar filtros avançados" para acessar opções adicionais:</p>
<ul>
<li><strong>Data inicial e Data final:</strong> Restringe a busca a diários publicados dentro do período informado.</li>
<li><strong>Resultados por página:</strong> Define quantos resultados serão exibidos em cada página.</li>
</ul>
</div>
<div class="mb-4">
<h6>Resultados da busca</h6>
<p>Nos resultados, você verá:</p>
<ul>
<li>Tipo e número do diário, com a data de publicação.</li>
<li>A página com melhor correspondência aparecerá destacada.</li>
<li>Para ver mais páginas do mesmo diário, clique no botão de expansão.</li>
<li>Use o botão "Ver Diário Completo" para abrir o arquivo PDF do diário inteiro.</li>
</ul>
</div>
<div class="mb-4">
<h6>Sugestões de busca</h6>
<p>Se o sistema encontrar um termo semelhante ao que você buscou, mostrará uma sugestão que você pode clicar para realizar uma nova busca com o termo sugerido.</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Entendi</button>
</div>
</div>
</div>
</div>
<!-- Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
<!-- Script da aplicação -->
<script src="script.js"></script>
<script src="{% static 'js/config.js' %}"></script>
<script src="{% static 'js/script.js' %}"></script>
</body>
</html>
</html>