feat: add cards view to calendar with 2-column grid layout
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

New "Karty" view shows up to 8 upcoming events as dark gradient cards
in a 2-column grid, matching the homepage event banner style.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-04-10 06:43:23 +02:00
parent 4576dbca04
commit 5dbd751722

View File

@ -549,6 +549,110 @@
font-size: var(--font-size-xl);
}
}
/* ===== WIDOK KART ===== */
.cards-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--spacing-md);
}
.card-banner {
background: linear-gradient(135deg, #1e3050 0%, #2E4872 100%);
border-radius: var(--radius-lg);
padding: var(--spacing-md);
color: white;
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
box-shadow: var(--shadow-md);
position: relative;
overflow: hidden;
text-decoration: none;
transition: var(--transition);
}
.card-banner:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(46, 72, 114, 0.25);
filter: brightness(1.05);
}
.card-banner::before {
content: '';
position: absolute;
top: -50%;
right: -10%;
width: 200px;
height: 200px;
background: rgba(255,255,255,0.06);
border-radius: 50%;
}
.card-banner.external {
background: linear-gradient(135deg, #334155 0%, #475569 100%);
}
.card-banner-title {
font-size: var(--font-size-base);
font-weight: 700;
line-height: 1.3;
}
.card-banner-meta {
font-size: var(--font-size-xs);
opacity: 0.9;
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
}
.card-banner-bottom {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-sm);
margin-top: auto;
}
.card-banner-attendees {
display: flex;
align-items: center;
gap: var(--spacing-xs);
background: rgba(255,255,255,0.15);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius);
font-weight: 600;
font-size: var(--font-size-xs);
}
.card-banner .btn-card {
background: white;
color: #2E4872;
border: none;
padding: var(--spacing-xs) var(--spacing-md);
font-weight: 600;
font-size: var(--font-size-sm);
border-radius: var(--radius-btn);
text-decoration: none;
cursor: pointer;
transition: var(--transition);
}
.card-banner .btn-card:hover {
background: #EDF0F5;
}
.card-banner .btn-card-registered {
background: #166534;
color: white;
}
@media (max-width: 768px) {
.cards-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
@ -625,6 +729,9 @@
<a href="?view=list" class="{% if view_mode == 'list' %}active{% endif %}">
Lista
</a>
<a href="?view=cards" class="{% if view_mode == 'cards' %}active{% endif %}">
Karty
</a>
<a href="?view=grid&year={{ year }}&month={{ month }}" class="{% if view_mode == 'grid' %}active{% endif %}">
Kalendarz
</a>
@ -713,6 +820,60 @@
<span><span class="badge-type external">Zewnętrzne</span></span>
</div>
{% elif view_mode == 'cards' %}
<!-- ================ WIDOK KART ================ -->
<div class="events-section">
<h2>Nadchodzące wydarzenia</h2>
{% if upcoming_events %}
<div class="cards-grid">
{% for event in upcoming_events[:8] %}
<a href="{{ url_for('calendar.calendar_event', event_id=event.id) }}" class="card-banner{% if event.is_external %} external{% endif %}">
<div class="card-banner-title">
{{ event.title }} →
{% if event.is_external and event.external_source %}
<span style="display:inline-block; background:rgba(255,255,255,0.2); color:#fff; font-size:10px; padding:2px 6px; border-radius:4px; font-weight:600; vertical-align:middle; margin-left:4px;">🌐 {{ event.external_source }}</span>
{% endif %}
</div>
<div class="card-banner-meta">
<span>📆 {{ event.event_date.strftime('%d.%m.%Y') }} ({{ ['Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob', 'Nd'][event.event_date.weekday()] }})</span>
{% if event.time_start %}
<span>🕕 {{ event.time_start.strftime('%H:%M') }}</span>
{% endif %}
{% if event.location %}
<span>📍 {{ event.location[:30] }}{% if event.location|length > 30 %}...{% endif %}</span>
{% endif %}
</div>
<div class="card-banner-bottom">
<div class="card-banner-attendees">
👥 Zapisanych: {{ event.total_attendee_count }} {% if event.total_attendee_count == 1 %}osoba{% elif event.total_attendee_count in [2,3,4] %}osoby{% else %}osób{% endif %}
</div>
<div>
{% set is_attending = event.attendees|selectattr('user_id','equalto',current_user.id)|list %}
{% if is_attending %}
<span class="btn-card btn-card-registered">✓ Zapisany/a</span>
{% else %}
<span class="btn-card">Zapisz się →</span>
{% endif %}
</div>
</div>
</a>
{% endfor %}
</div>
{% if upcoming_events|length > 8 %}
<div style="text-align: center; margin-top: var(--spacing-lg);">
<a href="?view=list" class="btn btn-outline">Zobacz wszystkie ({{ upcoming_events|length }}) →</a>
</div>
{% endif %}
{% else %}
<div class="empty-state">
<p>Brak nadchodzących wydarzeń</p>
</div>
{% endif %}
</div>
{% else %}
<!-- ================ WIDOK LISTY ================ -->