Zmieniono admin_dashboard i admin_zopk_dashboard na admin_zopk Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
442 lines
14 KiB
HTML
442 lines
14 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Fakty - Baza Wiedzy ZOPK{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: var(--spacing-xl);
|
||
flex-wrap: wrap;
|
||
gap: var(--spacing-md);
|
||
}
|
||
|
||
.page-header h1 {
|
||
font-size: var(--font-size-2xl);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
|
||
.breadcrumb {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-xs);
|
||
font-size: var(--font-size-sm);
|
||
color: var(--text-secondary);
|
||
margin-bottom: var(--spacing-md);
|
||
}
|
||
|
||
.breadcrumb a {
|
||
color: var(--text-secondary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.breadcrumb a:hover {
|
||
color: var(--primary);
|
||
}
|
||
|
||
.filters {
|
||
display: flex;
|
||
gap: var(--spacing-md);
|
||
margin-bottom: var(--spacing-lg);
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
background: var(--surface);
|
||
padding: var(--spacing-md);
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
.filter-btn {
|
||
padding: var(--spacing-sm) var(--spacing-md);
|
||
border-radius: var(--radius);
|
||
border: 1px solid var(--border);
|
||
background: var(--surface);
|
||
text-decoration: none;
|
||
color: var(--text-secondary);
|
||
font-size: var(--font-size-sm);
|
||
transition: var(--transition);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.filter-btn:hover {
|
||
background: var(--background);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.filter-btn.active {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: white;
|
||
}
|
||
|
||
.table-wrapper {
|
||
overflow-x: auto;
|
||
background: var(--surface);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: var(--shadow);
|
||
}
|
||
|
||
.data-table {
|
||
width: 100%;
|
||
min-width: 900px;
|
||
}
|
||
|
||
.data-table th,
|
||
.data-table td {
|
||
padding: var(--spacing-md);
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.data-table th {
|
||
background: var(--background);
|
||
font-weight: 600;
|
||
font-size: var(--font-size-sm);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.data-table tr:hover {
|
||
background: var(--background);
|
||
}
|
||
|
||
.fact-text {
|
||
max-width: 400px;
|
||
font-size: var(--font-size-sm);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.fact-structure {
|
||
font-size: var(--font-size-xs);
|
||
color: var(--text-secondary);
|
||
margin-top: var(--spacing-xs);
|
||
}
|
||
|
||
.fact-structure strong {
|
||
color: var(--primary);
|
||
}
|
||
|
||
.fact-source {
|
||
font-size: var(--font-size-xs);
|
||
color: var(--text-tertiary);
|
||
}
|
||
|
||
.fact-source a {
|
||
color: var(--primary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.fact-source a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.status-badge {
|
||
display: inline-block;
|
||
padding: 2px 8px;
|
||
border-radius: var(--radius-sm);
|
||
font-size: var(--font-size-xs);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.status-verified { background: #d1fae5; color: #065f46; }
|
||
.status-pending { background: #fef3c7; color: #92400e; }
|
||
|
||
.fact-type-badge {
|
||
padding: 2px 8px;
|
||
border-radius: var(--radius-sm);
|
||
font-size: var(--font-size-xs);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.fact-type-statistic { background: #dbeafe; color: #1e40af; }
|
||
.fact-type-event { background: #fce7f3; color: #be185d; }
|
||
.fact-type-statement { background: #e0e7ff; color: #3730a3; }
|
||
.fact-type-decision { background: #d1fae5; color: #065f46; }
|
||
.fact-type-milestone { background: #fef3c7; color: #92400e; }
|
||
.fact-type-financial { background: #fef3c7; color: #92400e; }
|
||
.fact-type-date { background: #f3e8ff; color: #7c3aed; }
|
||
|
||
.numeric-value {
|
||
font-weight: 600;
|
||
color: var(--primary);
|
||
}
|
||
|
||
.action-btn {
|
||
padding: var(--spacing-xs) var(--spacing-sm);
|
||
border-radius: var(--radius-sm);
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: var(--font-size-xs);
|
||
transition: var(--transition);
|
||
margin-right: var(--spacing-xs);
|
||
}
|
||
|
||
.action-btn-verify {
|
||
background: #d1fae5;
|
||
color: #065f46;
|
||
}
|
||
|
||
.action-btn-verify:hover {
|
||
background: #065f46;
|
||
color: white;
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: var(--spacing-xs);
|
||
padding: var(--spacing-lg);
|
||
}
|
||
|
||
.pagination a,
|
||
.pagination span {
|
||
padding: var(--spacing-sm) var(--spacing-md);
|
||
border-radius: var(--radius);
|
||
text-decoration: none;
|
||
font-size: var(--font-size-sm);
|
||
border: 1px solid var(--border);
|
||
background: var(--surface);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.pagination a:hover {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: white;
|
||
}
|
||
|
||
.pagination .current {
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color: white;
|
||
}
|
||
|
||
.pagination .disabled {
|
||
opacity: 0.5;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.stats-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: var(--spacing-md);
|
||
background: var(--background);
|
||
border-radius: var(--radius);
|
||
margin-bottom: var(--spacing-md);
|
||
font-size: var(--font-size-sm);
|
||
}
|
||
|
||
.entity-tag {
|
||
display: inline-block;
|
||
padding: 1px 6px;
|
||
background: var(--background);
|
||
border-radius: var(--radius-sm);
|
||
font-size: var(--font-size-xs);
|
||
margin: 1px;
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container">
|
||
<div class="breadcrumb">
|
||
<a href="{{ url_for('admin_zopk') }}">Panel Admina</a>
|
||
<span>›</span>
|
||
<a href="{{ url_for('admin_zopk') }}">ZOP Kaszubia</a>
|
||
<span>›</span>
|
||
<a href="{{ url_for('admin_zopk_knowledge_dashboard') }}">Baza Wiedzy</a>
|
||
<span>›</span>
|
||
<span>Fakty</span>
|
||
</div>
|
||
|
||
<div class="page-header">
|
||
<h1>📌 Fakty (wyekstraktowane informacje)</h1>
|
||
</div>
|
||
|
||
<!-- Filters -->
|
||
<div class="filters">
|
||
<span style="color: var(--text-secondary); font-size: var(--font-size-sm);">Typ faktu:</span>
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts') }}"
|
||
class="filter-btn {{ 'active' if current_fact_type is none else '' }}">
|
||
Wszystkie
|
||
</a>
|
||
{% for ftype in fact_types %}
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts', fact_type=ftype) }}"
|
||
class="filter-btn {{ 'active' if current_fact_type == ftype else '' }}">
|
||
{{ ftype }}
|
||
</a>
|
||
{% endfor %}
|
||
|
||
<span style="margin-left: 20px; color: var(--text-secondary); font-size: var(--font-size-sm);">Status:</span>
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts', is_verified='true', fact_type=current_fact_type) }}"
|
||
class="filter-btn {{ 'active' if is_verified == true else '' }}">
|
||
✓ Zweryfikowane
|
||
</a>
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts', is_verified='false', fact_type=current_fact_type) }}"
|
||
class="filter-btn {{ 'active' if is_verified == false else '' }}">
|
||
⏳ Oczekujące
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Stats Bar -->
|
||
<div class="stats-bar">
|
||
<span>Pokazuję {{ facts|length }} z {{ total }} faktów (strona {{ page }} z {{ pages }})</span>
|
||
<span>
|
||
{% if source_news_id %}
|
||
Źródło: Artykuł #{{ source_news_id }}
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts') }}" style="margin-left: 8px; color: var(--primary);">✕ usuń filtr</a>
|
||
{% endif %}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- Table -->
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Fakt</th>
|
||
<th>Typ</th>
|
||
<th>Wartość</th>
|
||
<th>Encje</th>
|
||
<th>Status</th>
|
||
<th>Źródło</th>
|
||
<th>Akcje</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for fact in facts %}
|
||
<tr id="fact-row-{{ fact.id }}">
|
||
<td>#{{ fact.id }}</td>
|
||
<td class="fact-text">
|
||
{{ fact.full_text }}
|
||
{% if fact.subject or fact.predicate or fact.object %}
|
||
<div class="fact-structure">
|
||
{% if fact.subject %}<strong>Podmiot:</strong> {{ fact.subject }}{% endif %}
|
||
{% if fact.predicate %}<strong>Relacja:</strong> {{ fact.predicate }}{% endif %}
|
||
{% if fact.object %}<strong>Obiekt:</strong> {{ fact.object[:50] }}{% if fact.object|length > 50 %}...{% endif %}{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if fact.fact_type %}
|
||
<span class="fact-type-badge fact-type-{{ fact.fact_type }}">{{ fact.fact_type }}</span>
|
||
{% else %}
|
||
<span style="color: var(--text-tertiary);">—</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if fact.numeric_value %}
|
||
<span class="numeric-value">{{ fact.numeric_value|round(2) }}</span>
|
||
{% if fact.numeric_unit %}{{ fact.numeric_unit }}{% endif %}
|
||
{% elif fact.date_value %}
|
||
<span class="numeric-value">{{ fact.date_value }}</span>
|
||
{% else %}
|
||
<span style="color: var(--text-tertiary);">—</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if fact.entities_involved %}
|
||
{% for e in fact.entities_involved[:3] %}
|
||
<span class="entity-tag">{{ e.name if e.name else e }}</span>
|
||
{% endfor %}
|
||
{% if fact.entities_involved|length > 3 %}
|
||
<span class="entity-tag">+{{ fact.entities_involved|length - 3 }}</span>
|
||
{% endif %}
|
||
{% else %}
|
||
<span style="color: var(--text-tertiary);">—</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if fact.is_verified %}
|
||
<span class="status-badge status-verified">✓ Zweryfikowany</span>
|
||
{% else %}
|
||
<span class="status-badge status-pending">⏳ Oczekuje</span>
|
||
{% endif %}
|
||
{% if fact.confidence_score %}
|
||
<br><small style="color: var(--text-tertiary);">{{ (fact.confidence_score * 100)|round }}% pewności</small>
|
||
{% endif %}
|
||
</td>
|
||
<td class="fact-source">
|
||
{% if fact.source_news_id %}
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts', source_news_id=fact.source_news_id) }}">
|
||
Art. #{{ fact.source_news_id }}
|
||
</a>
|
||
{% if fact.source_title %}
|
||
<br>{{ fact.source_title[:40] }}...
|
||
{% endif %}
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<button class="action-btn action-btn-verify" onclick="toggleVerify({{ fact.id }}, {{ 'false' if fact.is_verified else 'true' }})">
|
||
{{ '✗' if fact.is_verified else '✓' }}
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr>
|
||
<td colspan="8" style="text-align: center; padding: var(--spacing-xl); color: var(--text-secondary);">
|
||
Brak faktów do wyświetlenia
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Pagination -->
|
||
{% if pages > 1 %}
|
||
<div class="pagination">
|
||
{% if page > 1 %}
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts', page=page-1, fact_type=current_fact_type, is_verified=is_verified, source_news_id=source_news_id) }}">‹ Poprzednia</a>
|
||
{% else %}
|
||
<span class="disabled">‹ Poprzednia</span>
|
||
{% endif %}
|
||
|
||
{% for p in range(1, pages + 1) %}
|
||
{% if p == page %}
|
||
<span class="current">{{ p }}</span>
|
||
{% elif p <= 3 or p >= pages - 2 or (p >= page - 1 and p <= page + 1) %}
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts', page=p, fact_type=current_fact_type, is_verified=is_verified, source_news_id=source_news_id) }}">{{ p }}</a>
|
||
{% elif p == 4 or p == pages - 3 %}
|
||
<span>...</span>
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
{% if page < pages %}
|
||
<a href="{{ url_for('admin_zopk_knowledge_facts', page=page+1, fact_type=current_fact_type, is_verified=is_verified, source_news_id=source_news_id) }}">Następna ›</a>
|
||
{% else %}
|
||
<span class="disabled">Następna ›</span>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
async function toggleVerify(id, newState) {
|
||
try {
|
||
const response = await fetch(`/api/zopk/knowledge/facts/${id}/verify`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': '{{ csrf_token() }}'
|
||
},
|
||
body: JSON.stringify({ is_verified: newState })
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
location.reload();
|
||
} else {
|
||
alert('Błąd: ' + data.error);
|
||
}
|
||
} catch (error) {
|
||
alert('Błąd: ' + error.message);
|
||
}
|
||
}
|
||
{% endblock %}
|