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
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>
447 lines
15 KiB
HTML
447 lines
15 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}Encje - 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: 800px;
|
||
}
|
||
|
||
.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);
|
||
}
|
||
|
||
.entity-name {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.entity-description {
|
||
font-size: var(--font-size-xs);
|
||
color: var(--text-secondary);
|
||
margin-top: var(--spacing-xs);
|
||
max-width: 300px;
|
||
}
|
||
|
||
.entity-aliases {
|
||
font-size: var(--font-size-xs);
|
||
color: var(--text-tertiary);
|
||
margin-top: var(--spacing-xs);
|
||
}
|
||
|
||
.entity-type-badge {
|
||
padding: 2px 8px;
|
||
border-radius: var(--radius-sm);
|
||
font-size: var(--font-size-xs);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.entity-type-company { background: #dbeafe; color: #1e40af; }
|
||
.entity-type-person { background: #fce7f3; color: #be185d; }
|
||
.entity-type-place { background: #d1fae5; color: #065f46; }
|
||
.entity-type-organization { background: #fef3c7; color: #92400e; }
|
||
.entity-type-project { background: #e0e7ff; color: #3730a3; }
|
||
.entity-type-technology { background: #f3e8ff; color: #7c3aed; }
|
||
.entity-type-event { background: #fce7f3; color: #be185d; }
|
||
|
||
.mentions-count {
|
||
font-weight: 600;
|
||
font-size: var(--font-size-lg);
|
||
color: var(--primary);
|
||
}
|
||
|
||
.mentions-bar {
|
||
width: 100px;
|
||
height: 6px;
|
||
background: var(--border);
|
||
border-radius: 3px;
|
||
margin-top: var(--spacing-xs);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.mentions-bar-fill {
|
||
height: 100%;
|
||
background: var(--primary);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.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; }
|
||
|
||
.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;
|
||
}
|
||
|
||
.action-btn-link {
|
||
background: #dbeafe;
|
||
color: #1e40af;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.action-btn-link:hover {
|
||
background: #1e40af;
|
||
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-dates {
|
||
font-size: var(--font-size-xs);
|
||
color: var(--text-tertiary);
|
||
}
|
||
</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>Encje</span>
|
||
</div>
|
||
|
||
<div class="page-header">
|
||
<h1>🏢 Encje (rozpoznane byty)</h1>
|
||
</div>
|
||
|
||
<!-- Filters -->
|
||
<div class="filters">
|
||
<span style="color: var(--text-secondary); font-size: var(--font-size-sm);">Typ encji:</span>
|
||
<a href="{{ url_for('admin.admin_zopk_knowledge_entities') }}"
|
||
class="filter-btn {{ 'active' if current_entity_type is none else '' }}">
|
||
Wszystkie
|
||
</a>
|
||
{% for etype in entity_types %}
|
||
<a href="{{ url_for('admin.admin_zopk_knowledge_entities', entity_type=etype) }}"
|
||
class="filter-btn {{ 'active' if current_entity_type == etype else '' }}">
|
||
{% if etype == 'company' %}🏢{% elif etype == 'person' %}👤{% elif etype == 'place' %}📍{% elif etype == 'organization' %}🏛️{% elif etype == 'project' %}🚀{% elif etype == 'technology' %}💻{% else %}📋{% endif %}
|
||
{{ etype }}
|
||
</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_entities', is_verified='true', entity_type=current_entity_type) }}"
|
||
class="filter-btn {{ 'active' if is_verified == true else '' }}">
|
||
✓ Zweryfikowane
|
||
</a>
|
||
<a href="{{ url_for('admin.admin_zopk_knowledge_entities', is_verified='false', entity_type=current_entity_type) }}"
|
||
class="filter-btn {{ 'active' if is_verified == false else '' }}">
|
||
⏳ Oczekujące
|
||
</a>
|
||
|
||
<span style="margin-left: 20px; color: var(--text-secondary); font-size: var(--font-size-sm);">Min. wzmianek:</span>
|
||
<a href="{{ url_for('admin.admin_zopk_knowledge_entities', min_mentions=5, entity_type=current_entity_type) }}"
|
||
class="filter-btn {{ 'active' if min_mentions == 5 else '' }}">
|
||
5+
|
||
</a>
|
||
<a href="{{ url_for('admin.admin_zopk_knowledge_entities', min_mentions=10, entity_type=current_entity_type) }}"
|
||
class="filter-btn {{ 'active' if min_mentions == 10 else '' }}">
|
||
10+
|
||
</a>
|
||
<a href="{{ url_for('admin.admin_zopk_knowledge_entities', min_mentions=20, entity_type=current_entity_type) }}"
|
||
class="filter-btn {{ 'active' if min_mentions == 20 else '' }}">
|
||
20+
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Stats Bar -->
|
||
<div class="stats-bar">
|
||
<span>Pokazuję {{ entities|length }} z {{ total }} encji (strona {{ page }} z {{ pages }})</span>
|
||
<span>Posortowane według liczby wzmianek (malejąco)</span>
|
||
</div>
|
||
|
||
<!-- Table -->
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Nazwa</th>
|
||
<th>Typ</th>
|
||
<th>Wzmianki</th>
|
||
<th>Status</th>
|
||
<th>Daty</th>
|
||
<th>Akcje</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% set max_mentions = entities[0].mentions_count if entities else 1 %}
|
||
{% for entity in entities %}
|
||
<tr id="entity-row-{{ entity.id }}">
|
||
<td>#{{ entity.id }}</td>
|
||
<td>
|
||
<div class="entity-name">{{ entity.name }}</div>
|
||
{% if entity.short_description %}
|
||
<div class="entity-description">{{ entity.short_description }}</div>
|
||
{% endif %}
|
||
{% if entity.aliases and entity.aliases|length %}
|
||
<div class="entity-aliases">
|
||
Aliasy: {{ entity.aliases|join(', ') }}
|
||
</div>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<span class="entity-type-badge entity-type-{{ entity.entity_type }}">
|
||
{% if entity.entity_type == 'company' %}🏢{% elif entity.entity_type == 'person' %}👤{% elif entity.entity_type == 'place' %}📍{% elif entity.entity_type == 'organization' %}🏛️{% elif entity.entity_type == 'project' %}🚀{% elif entity.entity_type == 'technology' %}💻{% else %}📋{% endif %}
|
||
{{ entity.entity_type }}
|
||
</span>
|
||
</td>
|
||
<td>
|
||
<div class="mentions-count">{{ entity.mentions_count }}</div>
|
||
<div class="mentions-bar">
|
||
<div class="mentions-bar-fill" style="width: {{ (entity.mentions_count / max_mentions * 100)|round }}%"></div>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
{% if entity.is_verified %}
|
||
<span class="status-badge status-verified">✓ Zweryfikowana</span>
|
||
{% else %}
|
||
<span class="status-badge status-pending">⏳ Oczekuje</span>
|
||
{% endif %}
|
||
{% if entity.company_id %}
|
||
<br><a href="{{ url_for('company_detail', slug=entity.company_id) }}" class="action-btn-link" style="font-size: 10px;">→ Firma Norda</a>
|
||
{% endif %}
|
||
</td>
|
||
<td class="entity-dates">
|
||
{% if entity.first_mentioned_at %}
|
||
<div>Pierwsza: {{ entity.first_mentioned_at[:10] }}</div>
|
||
{% endif %}
|
||
{% if entity.last_mentioned_at %}
|
||
<div>Ostatnia: {{ entity.last_mentioned_at[:10] }}</div>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<button class="action-btn action-btn-verify" onclick="toggleVerify({{ entity.id }}, {{ 'false' if entity.is_verified else 'true' }})">
|
||
{{ '✗' if entity.is_verified else '✓' }}
|
||
</button>
|
||
{% if entity.external_url %}
|
||
<a href="{{ entity.external_url }}" target="_blank" class="action-btn action-btn-link">🔗</a>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% else %}
|
||
<tr>
|
||
<td colspan="7" style="text-align: center; padding: var(--spacing-xl); color: var(--text-secondary);">
|
||
Brak encji 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_entities', page=page-1, entity_type=current_entity_type, is_verified=is_verified, min_mentions=min_mentions) }}">‹ 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_entities', page=p, entity_type=current_entity_type, is_verified=is_verified, min_mentions=min_mentions) }}">{{ p }}</a>
|
||
{% elif p == 4 or p == pages - 3 %}
|
||
<span>...</span>
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
{% if page < pages %}
|
||
<a href="{{ url_for('admin.admin_zopk_knowledge_entities', page=page+1, entity_type=current_entity_type, is_verified=is_verified, min_mentions=min_mentions) }}">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/entities/${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 %}
|