nordabiz/templates/admin/membership.html
Maciej Pienczyn 0f8aca1435 feat: Add membership application system
Implement full online membership application workflow:
- 3-step wizard form with KRS/CEIDG auto-fill
- Admin panel for application review (approve/reject/request changes)
- Company data update requests for existing members
- Dashboard CTA for users without company
- API endpoints for NIP lookup and draft management

New files:
- database/migrations/042_membership_applications.sql
- blueprints/membership/ (routes, templates)
- blueprints/admin/routes_membership.py
- blueprints/api/routes_membership.py
- templates/membership/ and templates/admin/membership*.html

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 12:38:31 +01:00

283 lines
8.3 KiB
HTML

{% extends "base.html" %}
{% block title %}Deklaracje Członkowskie - 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;
}
.admin-header-content p {
margin: var(--spacing-xs) 0 0 0;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 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;
color: var(--primary);
}
.stat-card.submitted .stat-value { color: #3b82f6; }
.stat-card.under-review .stat-value { color: #a855f7; }
.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);
flex-wrap: wrap;
align-items: center;
}
.filter-group {
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.filter-group label {
font-size: var(--font-size-sm);
color: var(--text-secondary);
font-weight: 500;
}
.filter-select, .filter-input {
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);
margin-bottom: var(--spacing-xl);
}
.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);
}
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th, .data-table td {
padding: var(--spacing-md);
text-align: left;
border-bottom: 1px solid var(--border);
}
.data-table th {
font-weight: 600;
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.data-table tbody tr:hover {
background: var(--background);
}
.status-badge {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
padding: var(--spacing-xs) var(--spacing-md);
border-radius: var(--radius-full);
font-size: var(--font-size-sm);
font-weight: 500;
}
.status-draft { background: var(--warning-light); color: var(--warning); }
.status-submitted { background: rgba(59, 130, 246, 0.1); color: #3b82f6; }
.status-under_review { background: rgba(168, 85, 247, 0.1); color: #a855f7; }
.status-changes_requested { background: rgba(251, 146, 60, 0.1); color: #fb923c; }
.status-approved { background: var(--success-light); color: var(--success); }
.status-rejected { background: var(--error-light); color: var(--error); }
.btn-view {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
padding: var(--spacing-xs) var(--spacing-sm);
background: var(--primary);
color: white;
text-decoration: none;
border-radius: var(--radius);
font-size: var(--font-size-sm);
transition: var(--transition);
}
.btn-view:hover {
opacity: 0.9;
}
.empty-state {
text-align: center;
padding: var(--spacing-2xl);
color: var(--text-secondary);
}
@media (max-width: 768px) {
.data-table {
display: block;
overflow-x: auto;
}
}
</style>
{% endblock %}
{% block content %}
<div class="admin-header">
<div class="admin-header-content">
<h1>Deklaracje Członkowskie</h1>
<p class="text-muted">Zarządzanie zgłoszeniami przystąpienia do Izby</p>
</div>
<a href="{{ url_for('admin.admin_company_requests') }}" class="btn-view" style="padding: var(--spacing-sm) var(--spacing-lg);">
Zgłoszenia uzupełnienia danych →
</a>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">{{ total }}</div>
<div class="stat-label">Wszystkie</div>
</div>
<div class="stat-card submitted">
<div class="stat-value">{{ submitted }}</div>
<div class="stat-label">Wysłane</div>
</div>
<div class="stat-card under-review">
<div class="stat-value">{{ under_review }}</div>
<div class="stat-label">W rozpatrywaniu</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">
<div class="filter-group">
<label>Status:</label>
<select name="status" class="filter-select" onchange="this.form.submit()">
<option value="all" {% if current_status == 'all' %}selected{% endif %}>Wszystkie</option>
{% for value, label in status_choices %}
<option value="{{ value }}" {% if current_status == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="filter-group">
<label>Szukaj:</label>
<input type="text" name="q" class="filter-input" value="{{ search_query }}"
placeholder="Nazwa, NIP, email..." style="width: 200px;">
<button type="submit" class="btn-view">Szukaj</button>
</div>
</form>
<div class="section">
<h2>Lista deklaracji ({{ applications|length }})</h2>
{% if applications %}
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>Firma</th>
<th>NIP</th>
<th>Zgłaszający</th>
<th>Status</th>
<th>Data wysłania</th>
<th>Akcje</th>
</tr>
</thead>
<tbody>
{% for app in applications %}
<tr>
<td>{{ app.id }}</td>
<td>
<strong>{{ app.company_name or '-' }}</strong>
{% if app.registry_source %}
<br><small class="text-muted">z {{ app.registry_source }}</small>
{% endif %}
</td>
<td>{{ app.nip or '-' }}</td>
<td>
{{ app.user.name if app.user else '-' }}
<br><small class="text-muted">{{ app.email }}</small>
</td>
<td>
<span class="status-badge status-{{ app.status }}">
{{ app.status_label }}
</span>
</td>
<td>
{% if app.submitted_at %}
{{ app.submitted_at.strftime('%Y-%m-%d') }}
<br><small class="text-muted">{{ app.submitted_at.strftime('%H:%M') }}</small>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
<a href="{{ url_for('admin.admin_membership_detail', app_id=app.id) }}" class="btn-view">
Szczegóły
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="empty-state">
<p>Brak deklaracji spełniających kryteria wyszukiwania.</p>
</div>
{% endif %}
</div>
{% endblock %}