nordabiz/templates/admin/zopk_knowledge_facts.html
Maciej Pienczyn 094379d95e
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
fix(templates): Add blueprint prefix to url_for calls across admin templates
After refactoring to blueprints, templates still used bare endpoint names
(e.g., url_for('admin_zopk')) instead of prefixed names (e.g.,
url_for('admin.admin_zopk')). While most worked via backward-compat aliases,
api_zopk_search_news was missing from the alias list causing 500 on /admin/zopk.

Fixed 19 template files and added missing alias for api_zopk_search_news.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:44:50 +01:00

442 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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.admin_zopk') }}">Panel Admina</a>
<span></span>
<a href="{{ url_for('admin.admin_zopk') }}">ZOP Kaszubia</a>
<span></span>
<a href="{{ url_for('admin.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.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.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.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.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.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.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.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.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.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 %}