nordabiz/templates/admin/company_requests.html
Maciej Pienczyn 110d971dca
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
feat: migrate prod docs to OVH VPS + UTC→Warsaw timezone in all templates
Production moved from on-prem VM 249 (10.22.68.249) to OVH VPS
(57.128.200.27, inpi-vps-waw01). Updated ALL documentation, slash
commands, memory files, architecture docs, and deploy procedures.

Added |local_time Jinja filter (UTC→Europe/Warsaw) and converted
155 .strftime() calls across 71 templates so timestamps display
in Polish timezone regardless of server timezone.

Also includes: created_by_id tracking, abort import fix, ICS
calendar fix for missing end times, Pros Poland data cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 13:41:53 +02:00

381 lines
11 KiB
HTML

{% extends "base.html" %}
{% block title %}Zgłoszenia Uzupełnienia Danych - Admin - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.admin-header {
margin-bottom: var(--spacing-xl);
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.admin-header-content h1 {
font-size: var(--font-size-3xl);
color: var(--text-primary);
margin: 0;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-2xl);
}
.stat-card {
background: var(--surface);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
text-align: center;
}
.stat-value {
font-size: var(--font-size-3xl);
font-weight: 700;
}
.stat-card.pending .stat-value { color: var(--warning); }
.stat-card.approved .stat-value { color: var(--success); }
.stat-card.rejected .stat-value { color: var(--error); }
.stat-label {
color: var(--text-secondary);
font-size: var(--font-size-sm);
margin-top: var(--spacing-xs);
}
.filters-row {
display: flex;
gap: var(--spacing-md);
margin-bottom: var(--spacing-lg);
}
.filter-select {
padding: var(--spacing-xs) var(--spacing-sm);
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: var(--font-size-sm);
background: var(--surface);
}
.section {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
}
.section h2 {
font-size: var(--font-size-xl);
margin-bottom: var(--spacing-lg);
color: var(--text-primary);
border-bottom: 2px solid var(--border);
padding-bottom: var(--spacing-sm);
}
.request-card {
background: var(--background);
padding: var(--spacing-lg);
border-radius: var(--radius);
margin-bottom: var(--spacing-md);
border-left: 4px solid var(--warning);
}
.request-card.approved {
border-color: var(--success);
}
.request-card.rejected {
border-color: var(--error);
}
.request-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--spacing-md);
}
.request-company {
font-weight: 600;
font-size: var(--font-size-lg);
}
.request-nip {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.status-badge {
display: inline-flex;
padding: var(--spacing-xs) var(--spacing-md);
border-radius: var(--radius-full);
font-size: var(--font-size-sm);
font-weight: 500;
}
.status-pending { background: var(--warning-light); color: var(--warning); }
.status-approved { background: var(--success-light); color: var(--success); }
.status-rejected { background: var(--error-light); color: var(--error); }
.request-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
}
.info-item {
font-size: var(--font-size-sm);
}
.info-label {
color: var(--text-secondary);
}
.registry-data {
background: var(--surface);
padding: var(--spacing-md);
border-radius: var(--radius);
margin-bottom: var(--spacing-md);
}
.registry-data h4 {
margin: 0 0 var(--spacing-sm) 0;
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.registry-data-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: var(--spacing-xs);
font-size: var(--font-size-sm);
}
.request-actions {
display: flex;
gap: var(--spacing-sm);
}
.btn-approve, .btn-reject {
padding: var(--spacing-xs) var(--spacing-md);
border-radius: var(--radius);
font-size: var(--font-size-sm);
cursor: pointer;
border: none;
font-weight: 500;
}
.btn-approve {
background: var(--success);
color: white;
}
.btn-reject {
background: var(--error);
color: white;
}
.btn-approve:hover, .btn-reject:hover {
opacity: 0.9;
}
.empty-state {
text-align: center;
padding: var(--spacing-2xl);
color: var(--text-secondary);
}
.back-link {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
color: var(--text-secondary);
text-decoration: none;
margin-bottom: var(--spacing-lg);
}
.back-link:hover {
color: var(--primary);
}
</style>
{% endblock %}
{% block content %}
<a href="{{ url_for('admin.admin_membership') }}" class="back-link">
← Powrót do deklaracji członkowskich
</a>
<div class="admin-header">
<div class="admin-header-content">
<h1>Zgłoszenia Uzupełnienia Danych</h1>
<p class="text-muted">Weryfikacja danych z KRS/CEIDG dla istniejących firm</p>
</div>
</div>
<div class="stats-grid">
<div class="stat-card pending">
<div class="stat-value">{{ pending }}</div>
<div class="stat-label">Oczekujące</div>
</div>
<div class="stat-card approved">
<div class="stat-value">{{ approved }}</div>
<div class="stat-label">Zatwierdzone</div>
</div>
<div class="stat-card rejected">
<div class="stat-value">{{ rejected }}</div>
<div class="stat-label">Odrzucone</div>
</div>
</div>
<form method="GET" class="filters-row">
<select name="status" class="filter-select" onchange="this.form.submit()">
<option value="pending" {% if current_status == 'pending' %}selected{% endif %}>Oczekujące</option>
<option value="approved" {% if current_status == 'approved' %}selected{% endif %}>Zatwierdzone</option>
<option value="rejected" {% if current_status == 'rejected' %}selected{% endif %}>Odrzucone</option>
<option value="all" {% if current_status == 'all' %}selected{% endif %}>Wszystkie</option>
</select>
</form>
<div class="section">
<h2>Zgłoszenia ({{ requests|length }})</h2>
{% if requests %}
{% for req in requests %}
<div class="request-card {{ req.status }}">
<div class="request-header">
<div>
<div class="request-company">
{{ req.company.name if req.company else 'Nieznana firma' }}
</div>
<div class="request-nip">NIP: {{ req.nip }}</div>
</div>
<span class="status-badge status-{{ req.status }}">
{{ req.status_label }}
</span>
</div>
<div class="request-info">
<div class="info-item">
<span class="info-label">Zgłaszający:</span>
{{ req.user.name if req.user else '-' }} ({{ req.user.email if req.user else '-' }})
</div>
<div class="info-item">
<span class="info-label">Typ:</span>
{{ req.request_type_label }}
</div>
<div class="info-item">
<span class="info-label">Data:</span>
{{ req.created_at|local_time('%Y-%m-%d %H:%M') if req.created_at else '-' }}
</div>
<div class="info-item">
<span class="info-label">Źródło:</span>
{{ req.registry_source or 'Brak' }}
</div>
</div>
{% if req.fetched_data %}
<div class="registry-data">
<h4>Dane z rejestru {{ req.registry_source }}:</h4>
<div class="registry-data-grid">
{% if req.fetched_data.name %}
<div><strong>Nazwa:</strong> {{ req.fetched_data.name }}</div>
{% endif %}
{% if req.fetched_data.regon %}
<div><strong>REGON:</strong> {{ req.fetched_data.regon }}</div>
{% endif %}
{% if req.fetched_data.krs %}
<div><strong>KRS:</strong> {{ req.fetched_data.krs }}</div>
{% endif %}
{% if req.fetched_data.address_city %}
<div><strong>Miasto:</strong> {{ req.fetched_data.address_city }}</div>
{% endif %}
</div>
</div>
{% endif %}
{% if req.user_note %}
<div style="margin-bottom: var(--spacing-md);">
<strong>Notatka:</strong> {{ req.user_note }}
</div>
{% endif %}
{% if req.status == 'pending' %}
<div class="request-actions">
<button class="btn-approve" onclick="approveRequest({{ req.id }})">
✓ Zatwierdź i zaktualizuj
</button>
<button class="btn-reject" onclick="rejectRequest({{ req.id }})">
✗ Odrzuć
</button>
</div>
{% elif req.review_comment %}
<div style="font-size: var(--font-size-sm); color: var(--text-secondary);">
<strong>Komentarz:</strong> {{ req.review_comment }}
</div>
{% endif %}
{% if req.applied_fields %}
<div style="font-size: var(--font-size-sm); color: var(--success); margin-top: var(--spacing-sm);">
<strong>Zaktualizowane pola:</strong> {{ req.applied_fields|join(', ') }}
</div>
{% endif %}
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<p>Brak zgłoszeń w wybranej kategorii.</p>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
async function approveRequest(id) {
if (!confirm('Czy na pewno chcesz zatwierdzić to zgłoszenie i zaktualizować dane firmy?')) {
return;
}
try {
const response = await fetch(`/admin/company-requests/${id}/approve`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const result = await response.json();
if (result.success) {
alert('Zgłoszenie zatwierdzone. Zaktualizowane pola: ' + (result.applied_fields || []).join(', '));
location.reload();
} else {
alert(result.error || 'Błąd');
}
} catch (e) {
alert('Błąd połączenia');
}
}
async function rejectRequest(id) {
const comment = prompt('Podaj powód odrzucenia (opcjonalnie):');
if (comment === null) return;
try {
const response = await fetch(`/admin/company-requests/${id}/reject`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ comment: comment })
});
const result = await response.json();
if (result.success) {
location.reload();
} else {
alert(result.error || 'Błąd');
}
} catch (e) {
alert('Błąd połączenia');
}
}
{% endblock %}