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>
283 lines
8.3 KiB
HTML
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 %}
|