feat(membership): visual tracker + event history — parcel tracking style
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

This commit is contained in:
Maciej Pienczyn 2026-03-30 15:08:26 +02:00
parent 73abb76c9e
commit 8441dc47db

View File

@ -538,6 +538,110 @@
border-radius: var(--radius);
}
/* Tracker — parcel tracking style */
.tracker-bar {
display: flex;
align-items: flex-start;
justify-content: center;
padding: 16px 0;
}
.tracker-step {
display: flex;
flex-direction: column;
align-items: center;
min-width: 90px;
}
.tracker-dot {
width: 40px;
height: 40px;
border-radius: 50%;
background: #e2e8f0;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #94a3b8;
border: 3px solid #e2e8f0;
transition: all 0.3s;
}
.tracker-step.done .tracker-dot {
background: #16a34a;
border-color: #16a34a;
color: white;
font-size: 14px;
}
.tracker-step.current .tracker-dot {
background: #2563eb;
border-color: #2563eb;
color: white;
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.2);
}
.tracker-step.rejected .tracker-dot {
background: #dc2626;
border-color: #dc2626;
color: white;
box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.2);
}
.tracker-label {
margin-top: 6px;
font-size: 11px;
font-weight: 600;
color: #94a3b8;
text-align: center;
}
.tracker-step.done .tracker-label,
.tracker-step.current .tracker-label { color: #1e293b; }
.tracker-step.rejected .tracker-label { color: #dc2626; }
.tracker-line {
flex: 1;
height: 3px;
background: #e2e8f0;
margin-top: 20px;
min-width: 30px;
}
.tracker-line.done { background: #16a34a; }
.tracker-note {
text-align: center;
padding: 8px 12px;
border-radius: 8px;
font-size: 12px;
margin-top: 8px;
}
.tracker-note.warning { background: #fef3c7; color: #92400e; }
.tracker-note.info { background: #dbeafe; color: #1e40af; }
/* History list */
.history-list { padding-left: 8px; }
.history-item {
display: flex;
gap: 10px;
padding: 8px 0;
border-left: 2px solid #e2e8f0;
padding-left: 16px;
margin-left: 6px;
position: relative;
}
.history-item:last-child { border-left-color: transparent; }
.history-dot {
width: 14px;
height: 14px;
border-radius: 50%;
flex-shrink: 0;
position: absolute;
left: -8px;
top: 12px;
}
.dot-blue { background: #3b82f6; }
.dot-yellow { background: #eab308; }
.dot-green { background: #16a34a; }
.dot-red { background: #dc2626; }
.dot-orange { background: #f97316; }
.dot-gray { background: #94a3b8; }
.history-content { flex: 1; }
.history-title { font-weight: 600; font-size: 13px; color: #1e293b; }
.history-meta { font-size: 11px; color: #94a3b8; margin-top: 2px; }
.history-comment { font-size: 12px; color: #64748b; font-style: italic; margin-top: 4px; }
/* Print styles */
@media print {
/* NUCLEAR: hide everything that isn't .print-header or .main-content */
@ -970,79 +1074,93 @@
{% endif %}
</div>
<!-- Historia workflow -->
{% if application.workflow_history %}
<!-- Tracker statusu deklaracji — styl "śledzenie paczki" -->
<div class="section">
<h2>Historia workflow</h2>
<div class="workflow-timeline">
{% for event in application.workflow_history %}
{% if event.event == 'admin_proposed_changes' %}
<div class="workflow-event sub-event">
<div class="workflow-event-title">
<span class="workflow-arrow"></span>
Propozycja zmian
</div>
<div class="workflow-event-meta">
Od: <strong>{{ event.user_name }}</strong>
<br>{{ event.timestamp[:16].replace('T', ' ') }}
{% if event.details.source %}
<br>Źródło: {{ event.details.source }}
<h2>Status deklaracji</h2>
<div class="tracker">
{% set steps = [
{'key': 'submitted', 'icon': '📝', 'label': 'Złożona'},
{'key': 'under_review', 'icon': '🔍', 'label': 'Rozpatrywana'},
{'key': 'approved', 'icon': '✅', 'label': 'Zatwierdzona'},
] %}
{% set status_order = {'draft': 0, 'submitted': 1, 'under_review': 2, 'changes_requested': 2, 'pending_user_approval': 2, 'approved': 3, 'rejected': 3} %}
{% set current_step = status_order.get(application.status, 0) %}
<div class="tracker-bar">
{% for step in steps %}
{% set step_num = loop.index %}
{% set is_done = step_num <= current_step %}
{% set is_current = step_num == current_step %}
{% set is_rejected = application.status == 'rejected' and step_num == 3 %}
<div class="tracker-step {{ 'done' if is_done else '' }} {{ 'current' if is_current else '' }} {{ 'rejected' if is_rejected else '' }}">
<div class="tracker-dot">
{% if is_done and not is_current %}
<span></span>
{% elif is_current %}
<span>{{ step.icon }}</span>
{% else %}
<span>{{ loop.index }}</span>
{% endif %}
</div>
{% if event.details.comment %}
<div class="workflow-event-comment">
„{{ event.details.comment }}"
</div>
{% endif %}
{% if event.details.changes %}
<div style="font-size: var(--font-size-xs); margin-top: var(--spacing-xs); color: var(--text-secondary);">
{% for change in event.details.changes %}
<div>{{ change }}</div>
{% endfor %}
</div>
{% endif %}
<div class="tracker-label">{{ step.label }}</div>
</div>
{% elif event.event == 'user_accepted_changes' %}
<div class="workflow-event sub-event">
<div class="workflow-event-title">
<span class="workflow-arrow"></span>
Zmiany zaakceptowane
</div>
<div class="workflow-event-meta">
Przez: <strong>{{ event.user_name }}</strong>
<br>{{ event.timestamp[:16].replace('T', ' ') }}
</div>
</div>
{% elif event.event == 'user_rejected_changes' %}
<div class="workflow-event sub-event" style="border-color: var(--warning);">
<div class="workflow-event-title" style="color: var(--warning);">
<span class="workflow-arrow" style="color: var(--warning);"></span>
Zmiany odrzucone
</div>
<div class="workflow-event-meta">
Przez: <strong>{{ event.user_name }}</strong>
<br>{{ event.timestamp[:16].replace('T', ' ') }}
</div>
{% if event.details.reason %}
<div class="workflow-event-comment">
„{{ event.details.reason }}"
</div>
{% endif %}
{% if not loop.last %}
<div class="tracker-line {{ 'done' if step_num < current_step else '' }}"></div>
{% endif %}
{% endfor %}
{% if application.status == 'rejected' %}
<div class="tracker-line"></div>
<div class="tracker-step current rejected">
<div class="tracker-dot"><span></span></div>
<div class="tracker-label">Odrzucona</div>
</div>
{% endif %}
{% endfor %}
</div>
{# Current pending status #}
{% if application.status == 'pending_user_approval' %}
<div class="workflow-event sub-event pending">
<div class="workflow-event-title">
<span class="workflow-arrow"></span>
Oczekuje na akceptację użytkownika
</div>
{% if application.status == 'changes_requested' %}
<div class="tracker-note warning">
⚠️ Poproszono o poprawki — oczekuje na korektę od zgłaszającego
</div>
{% elif application.status == 'pending_user_approval' %}
<div class="tracker-note info">
⏳ Zaproponowano zmiany z rejestru — oczekuje na akceptację użytkownika
</div>
{% endif %}
</div>
</div>
<!-- Historia zdarzeń (szczegóły) -->
{% if application.workflow_history %}
<div class="section">
<h2>Historia zdarzeń</h2>
<div class="history-list">
{% for event in application.workflow_history|reverse %}
<div class="history-item">
<div class="history-dot
{% if event.event == 'submitted' %}dot-blue
{% elif event.event == 'start_review' %}dot-yellow
{% elif event.event == 'approved' %}dot-green
{% elif event.event == 'rejected' %}dot-red
{% elif event.event == 'changes_requested' %}dot-orange
{% else %}dot-gray{% endif %}
"></div>
<div class="history-content">
<div class="history-title">{{ event.get('action_label', event.event) }}</div>
<div class="history-meta">
{{ event.timestamp[:16].replace('T', ' ') }}
{% if event.user_name %} · {{ event.user_name }}{% endif %}
</div>
{% if event.get('details', {}).get('comment') %}
<div class="history-comment">„{{ event.details.comment }}"</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Info o zgłaszającym -->