nordabiz/templates/calendar/index.html
Maciej Pienczyn 60c43cd8a0
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
Add red color for featured/important events in calendar
Featured events (is_featured=true) now display in red (#fee2e2)
instead of inheriting their type color. Applies to grid cells,
list view (red left border), bottom sheet modal dots, and legend.
Currently featured: portal presentation (Apr 9), nuclear meeting
with Governor (Apr 7), conference (Apr 9), gala (Nov 21).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 16:08:28 +01:00

922 lines
32 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Kalendarz - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.calendar-header {
margin-bottom: var(--spacing-lg);
}
.calendar-header h1 {
font-size: var(--font-size-3xl);
color: var(--text-primary);
}
/* Toolbar z przyciskami widoku */
.calendar-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-xl);
flex-wrap: wrap;
gap: var(--spacing-md);
background: var(--surface);
padding: var(--spacing-md);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
}
.view-toggle {
display: flex;
gap: 2px;
background: var(--border);
border-radius: var(--radius);
padding: 2px;
}
.view-toggle a {
padding: var(--spacing-sm) var(--spacing-md);
text-decoration: none;
color: var(--text-secondary);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
font-weight: 500;
transition: var(--transition);
}
.view-toggle a.active {
background: var(--primary);
color: white;
}
.view-toggle a:not(.active):hover {
background: var(--surface);
color: var(--text-primary);
}
.month-nav {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.month-nav a {
padding: var(--spacing-xs) var(--spacing-sm);
text-decoration: none;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
transition: var(--transition);
}
.month-nav a:hover {
background: var(--surface-hover, #f3f4f6);
color: var(--text-primary);
}
.current-month {
font-weight: 600;
font-size: var(--font-size-lg);
min-width: 160px;
text-align: center;
color: var(--text-primary);
}
/* Siatka kalendarza */
.calendar-grid {
background: var(--surface);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow);
}
.calendar-header-row {
display: grid;
grid-template-columns: repeat(7, 1fr);
}
.day-header {
padding: var(--spacing-sm);
text-align: center;
font-weight: 600;
font-size: var(--font-size-sm);
background: var(--primary);
color: white;
}
.day-header.weekend {
background: #1e40af;
}
.calendar-week {
display: grid;
grid-template-columns: repeat(7, 1fr);
}
.calendar-day {
min-height: 110px;
padding: var(--spacing-xs);
border: 1px solid var(--border);
border-top: none;
background: var(--background);
vertical-align: top;
}
.calendar-day:not(:last-child) {
border-right: none;
}
.calendar-day.empty {
background: #f9fafb;
}
.calendar-day.today {
background: #eff6ff;
border-color: var(--primary);
border-width: 2px;
}
.calendar-day.weekend {
background: #fafafa;
}
.day-number {
font-weight: 600;
font-size: var(--font-size-sm);
margin-bottom: var(--spacing-xs);
color: var(--text-secondary);
}
.calendar-day.today .day-number {
color: var(--primary);
}
.calendar-event {
display: block;
padding: 3px 6px;
margin-bottom: 3px;
border-radius: var(--radius-sm);
font-size: 11px;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: var(--transition);
}
.calendar-event:hover {
opacity: 0.8;
transform: scale(1.02);
}
.calendar-event.featured {
background: #fee2e2;
color: #991b1b;
}
.calendar-event.meeting {
background: #dbeafe;
color: #1e40af;
}
.calendar-event.networking {
background: #fef3c7;
color: #92400e;
}
.calendar-event.webinar {
background: #dcfce7;
color: #166534;
}
.calendar-event.other {
background: #f3e8ff;
color: #7c3aed;
}
.calendar-event.external {
background: #fff7ed;
color: #c2410c;
border-left: 2px solid #fb923c;
}
/* Widok listy - istniejące style */
.events-section {
margin-bottom: var(--spacing-2xl);
}
.events-section h2 {
font-size: var(--font-size-xl);
margin-bottom: var(--spacing-lg);
color: var(--text-primary);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.event-card {
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-md);
box-shadow: var(--shadow);
display: flex;
gap: var(--spacing-lg);
transition: var(--transition);
}
.event-card:hover {
box-shadow: var(--shadow-md);
}
.event-card.featured {
border-left: 4px solid #ef4444;
}
.event-card.past {
opacity: 0.7;
}
.event-date-box {
min-width: 70px;
text-align: center;
background: var(--primary);
color: white;
border-radius: var(--radius);
padding: var(--spacing-sm);
}
.event-date-box .day {
font-size: var(--font-size-2xl);
font-weight: 700;
line-height: 1;
}
.event-date-box .month {
font-size: var(--font-size-sm);
text-transform: uppercase;
}
.event-info {
flex: 1;
}
.event-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.event-title a {
color: inherit;
text-decoration: none;
}
.event-title a:hover {
color: var(--primary);
}
.event-meta {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-md);
font-size: var(--font-size-sm);
color: var(--text-secondary);
margin-bottom: var(--spacing-sm);
}
.event-meta span {
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.event-description {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.event-actions {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.attendee-count {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.empty-state {
text-align: center;
padding: var(--spacing-2xl);
color: var(--text-secondary);
background: var(--surface);
border-radius: var(--radius-lg);
}
.badge-type {
display: inline-block;
padding: 2px 8px;
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: 500;
text-transform: uppercase;
}
.badge-type.meeting { background: #dbeafe; color: #1e40af; }
.badge-type.webinar { background: #dcfce7; color: #166534; }
.badge-type.networking { background: #fef3c7; color: #92400e; }
.badge-type.other { background: #f3e8ff; color: #7c3aed; }
.badge-type.external { background: #fff7ed; color: #c2410c; }
.event-card.external-event {
border-left: 3px solid #94a3b8;
opacity: 0.92;
}
.event-card.external-event .event-date-box {
background: #64748b;
}
.external-source {
font-size: var(--font-size-xs);
color: var(--text-secondary);
font-style: italic;
}
.filter-toggle {
display: flex;
align-items: center;
gap: var(--spacing-xs);
font-size: var(--font-size-sm);
color: var(--text-secondary);
cursor: pointer;
user-select: none;
}
.filter-toggle input { width: auto; }
.rsvp-list-btn { min-width: 110px; transition: var(--transition); }
.rsvp-list-btn.rsvp-attending {
background: #059669;
border-color: #059669;
color: white;
}
.rsvp-list-btn.rsvp-attending:hover {
background: #dc2626;
border-color: #dc2626;
}
.rsvp-list-btn.rsvp-not-attending {
background: #2563eb;
border-color: #2563eb;
color: white;
}
.rsvp-list-btn.rsvp-not-attending:hover {
background: #1d4ed8;
border-color: #1d4ed8;
}
/* Responsywność */
@media (max-width: 768px) {
.calendar-toolbar {
flex-direction: column;
align-items: stretch;
}
.month-nav {
justify-content: center;
}
.calendar-day {
min-height: 80px;
padding: 2px;
}
.calendar-event {
font-size: 10px;
padding: 2px 4px;
}
.day-header {
font-size: 9px;
padding: 2px;
letter-spacing: -0.5px;
}
.calendar-day {
min-height: 50px;
padding: 0;
display: flex;
flex-direction: column;
position: relative;
}
.day-number {
font-size: 11px;
margin: 0;
padding: 2px 4px;
position: relative;
z-index: 1;
}
/* Events fill the entire cell as colored strips */
.calendar-day .calendar-events-wrap {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.calendar-event {
font-size: 0;
padding: 0;
margin: 0;
white-space: normal;
border-radius: 0;
width: 100%;
flex: 1;
display: block;
border-left: none;
min-height: 12px;
}
/* Single event — fills entire cell below day number */
.calendar-day.has-1-event {
padding: 0;
}
.calendar-day.has-1-event .calendar-event {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
min-height: auto;
}
.calendar-day.has-1-event .day-number {
color: inherit;
}
.calendar-day.empty { background: #f9fafb; }
.calendar-day.has-events { cursor: pointer; }
/* Day modal - bottom sheet */
.day-modal-overlay {
display: none;
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.4);
z-index: 9998;
}
.day-modal-overlay.show { display: block; }
.day-modal {
display: block;
position: fixed;
bottom: 0; left: 0; right: 0;
background: white;
border-radius: 16px 16px 0 0;
box-shadow: 0 -4px 20px rgba(0,0,0,0.15);
z-index: 9999;
padding-bottom: env(safe-area-inset-bottom, 16px);
visibility: hidden;
transform: translateY(100%);
transition: transform 0.25s ease-out, visibility 0s 0.25s;
pointer-events: none;
max-height: 70vh;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.day-modal.show {
visibility: visible;
transform: translateY(0);
transition: transform 0.25s ease-out, visibility 0s 0s;
pointer-events: auto;
}
.day-modal-handle {
width: 36px; height: 4px;
background: #d1d5db; border-radius: 2px;
margin: 10px auto 4px;
}
.day-modal-title {
padding: 8px 16px 12px;
font-size: 17px; font-weight: 600;
border-bottom: 1px solid var(--border);
}
.day-modal-event {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
text-decoration: none;
color: var(--text-primary);
border-bottom: 1px solid #f1f5f9;
transition: background 0.15s;
}
.day-modal-event:active { background: #f1f5f9; }
.day-modal-event-dot {
width: 12px; height: 12px;
border-radius: 3px; flex-shrink: 0;
}
.day-modal-event-dot.featured { background: #ef4444; }
.day-modal-event-dot.meeting { background: #3b82f6; }
.day-modal-event-dot.networking { background: #f59e0b; }
.day-modal-event-dot.webinar { background: #22c55e; }
.day-modal-event-dot.other { background: #a855f7; }
.day-modal-event-dot.external { background: #fb923c; }
.day-modal-event-info { flex: 1; }
.day-modal-event-title { font-weight: 600; font-size: 15px; }
.day-modal-event-meta { font-size: 13px; color: var(--text-secondary); margin-top: 2px; }
.day-modal-event-arrow { color: #94a3b8; font-size: 18px; }
.event-card {
flex-wrap: wrap;
}
.event-actions {
width: 100%;
justify-content: flex-start;
padding-top: var(--spacing-sm);
border-top: 1px solid var(--border);
margin-top: var(--spacing-sm);
}
.calendar-header h1 {
font-size: var(--font-size-xl);
}
}
</style>
{% endblock %}
{% set pl_months = {'Jan':'sty','Feb':'lut','Mar':'mar','Apr':'kwi','May':'maj','Jun':'cze','Jul':'lip','Aug':'sie','Sep':'wrz','Oct':'paź','Nov':'lis','Dec':'gru'} %}
{% set pl_types = {'meeting':'Spotkanie','networking':'Networking','webinar':'Webinar','other':'Inne','conference':'Konferencja','workshop':'Warsztaty'} %}
{% block content %}
<div class="calendar-header">
<h1>Kalendarz wydarzeń</h1>
<div style="display: flex; align-items: center; gap: var(--spacing-md); flex-wrap: wrap;">
<p class="text-muted" style="margin: 0;">Spotkania i wydarzenia Norda Biznes</p>
<button onclick="showSubscribeModal()" style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; border-radius: var(--radius); font-size: 13px; cursor: pointer; font-weight: 500; white-space: nowrap;">
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
Subskrybuj kalendarz
</button>
</div>
</div>
<!-- Subscribe modal -->
<div id="subscribeModal" style="display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:9999; align-items:center; justify-content:center;">
<div style="background:white; border-radius:var(--radius-xl); padding:24px; max-width:500px; width:90%; margin:auto; position:relative; top:50%; transform:translateY(-50%);">
<button onclick="document.getElementById('subscribeModal').style.display='none'" style="position:absolute; top:12px; right:16px; background:none; border:none; font-size:20px; cursor:pointer; color:#64748b;">&times;</button>
<h3 style="margin:0 0 8px 0; font-size:18px;">Dodaj wydarzenia do kalendarza</h3>
<p style="color:#64748b; font-size:13px; line-height:1.5; margin-bottom:16px;">
Skopiuj poniższy link i dodaj go jako subskrypcję w swoim kalendarzu.
Wydarzenia Izby będą się automatycznie synchronizować.
</p>
<div style="display:flex; gap:8px; margin-bottom:16px;">
<input id="icalUrl" readonly value="https://nordabiznes.pl/kalendarz/ical" style="flex:1; padding:8px 12px; border:1px solid #d1d5db; border-radius:var(--radius); font-size:13px; background:#f9fafb; font-family:monospace;">
<button onclick="copyIcalUrl()" id="copyBtn" style="padding:8px 16px; background:#1d4ed8; color:white; border:none; border-radius:var(--radius); font-size:13px; cursor:pointer; font-weight:500; white-space:nowrap;">Kopiuj</button>
</div>
<div style="font-size:12px; color:#64748b; line-height:1.6;">
<strong>Jak dodać:</strong><br>
<strong>iPhone/Mac:</strong> Ustawienia → Konta → Dodaj konto → Inne → Subskrypcja kalendarza → wklej link<br>
<strong>Google Calendar:</strong> Inne kalendarze (+) → Z adresu URL → wklej link<br>
<strong>Outlook:</strong> Dodaj kalendarz → Subskrybuj z internetu → wklej link
</div>
</div>
</div>
<!-- Toolbar z przyciskami widoku -->
<div class="calendar-toolbar">
<div class="view-toggle">
<a href="?view=list" class="{% if view_mode == 'list' %}active{% endif %}">
Lista
</a>
<a href="?view=grid&year={{ year }}&month={{ month }}" class="{% if view_mode == 'grid' %}active{% endif %}">
Kalendarz
</a>
</div>
{% if view_mode == 'grid' %}
<div class="month-nav">
<a href="?view=grid&year={{ prev_year }}&month={{ prev_month }}">&larr; Poprzedni</a>
<span class="current-month">{{ month_name }} {{ year }}</span>
<a href="?view=grid&year={{ next_year }}&month={{ next_month }}">Następny &rarr;</a>
</div>
{% endif %}
<label class="filter-toggle" id="external-filter-label" style="display: none;">
<input type="checkbox" id="show-external" checked>
<span id="external-filter-text">Pokaż zewnętrzne</span>
</label>
{% if current_user.can_access_admin_panel() %}
<a href="{{ url_for('admin.admin_calendar') }}" class="btn btn-secondary btn-sm">Zarządzaj</a>
{% endif %}
</div>
{% if view_mode == 'grid' %}
<!-- ================ WIDOK SIATKI ================ -->
<div class="calendar-grid">
<!-- Nagłówki dni tygodnia -->
<div class="calendar-header-row">
<div class="day-header">Pon</div>
<div class="day-header">Wt</div>
<div class="day-header">Sr</div>
<div class="day-header">Czw</div>
<div class="day-header">Pt</div>
<div class="day-header weekend">Sob</div>
<div class="day-header weekend">Nd</div>
</div>
<!-- Tygodnie -->
{% for week in month_days %}
<div class="calendar-week">
{% for day in week %}
{% set is_weekend = loop.index > 5 %}
{% set day_events = events_by_day.get(day, []) if day != 0 else [] %}
<div class="calendar-day {% if day == 0 %}empty{% endif %}{% if day == today.day and month == today.month and year == today.year %} today{% endif %}{% if is_weekend and day != 0 %} weekend{% endif %}{% if day_events %} has-events has-{{ day_events|length }}-event{% endif %}"
{% if day_events %}onclick="openDayModal({{ day }}, {{ month }}, {{ year }})"{% endif %}
data-day="{{ day }}">
{% if day != 0 %}
<div class="day-number">{{ day }}</div>
{% if day_events %}
<div class="calendar-events-wrap">
{% for event in day_events %}
<div class="calendar-event {{ 'featured' if event.is_featured else ('external' if event.is_external else event.event_type) }}"
data-event-id="{{ event.id }}"
data-event-title="{{ event.title }}"
data-event-time="{% if event.time_start %}{{ event.time_start.strftime('%H:%M') }}{% if event.time_end %}-{{ event.time_end.strftime('%H:%M') }}{% endif %}{% endif %}"
data-event-type="{{ 'Zewnętrzne' if event.is_external else ({'meeting':'Spotkanie','networking':'Networking','webinar':'Webinar','conference':'Konferencja','workshop':'Warsztaty'}.get(event.event_type, event.event_type)) }}"
data-event-location="{{ event.location or '' }}"
data-event-url="{{ url_for('calendar.calendar_event', event_id=event.id) }}">
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
<!-- Day events modal (bottom sheet) -->
<div class="day-modal-overlay" id="dayModalOverlay" onclick="closeDayModal()"></div>
<div class="day-modal" id="dayModal">
<div class="day-modal-handle"></div>
<div class="day-modal-title" id="dayModalTitle"></div>
<div id="dayModalEvents"></div>
</div>
<!-- Legenda typów wydarzeń -->
<div style="margin-top: var(--spacing-lg); display: flex; gap: var(--spacing-lg); flex-wrap: wrap; font-size: var(--font-size-sm); color: var(--text-secondary);">
<span><span class="badge-type" style="background:#fee2e2;color:#991b1b;">Ważne</span></span>
<span><span class="badge-type meeting">Spotkanie</span></span>
<span><span class="badge-type networking">Networking</span></span>
<span><span class="badge-type webinar">Webinar</span></span>
<span><span class="badge-type other">Inne</span></span>
<span><span class="badge-type external">Zewnętrzne</span></span>
</div>
{% else %}
<!-- ================ WIDOK LISTY ================ -->
<!-- Nadchodzące wydarzenia -->
<div class="events-section">
<h2>Nadchodzące wydarzenia</h2>
{% if upcoming_events %}
{% for event in upcoming_events %}
<div class="event-card {% if event.is_featured %}featured{% endif %}{% if event.is_external %} external-event{% endif %}" data-external="{{ 'true' if event.is_external else 'false' }}">
<div class="event-date-box">
<div class="day">{{ event.event_date.day }}</div>
<div class="month">{{ pl_months.get(event.event_date.strftime('%b'), event.event_date.strftime('%b')) }}</div>
</div>
<div class="event-info">
<div class="event-title">
<a href="{{ url_for('calendar.calendar_event', event_id=event.id) }}">{{ event.title }}</a>{% if event.is_external %} <span style="display:inline-block;background:#94a3b8;color:#fff;font-size:10px;padding:1px 5px;border-radius:3px;font-weight:600;vertical-align:middle;">ZEWNĘTRZNE</span>{% elif event.access_level == 'admin_only' %} <span style="display:inline-block;background:#ef4444;color:#fff;font-size:10px;padding:1px 5px;border-radius:3px;font-weight:600;vertical-align:middle;">UKRYTE</span>{% elif event.access_level == 'rada_only' %} <span style="display:inline-block;background:#f59e0b;color:#92400e;font-size:10px;padding:1px 5px;border-radius:3px;font-weight:600;vertical-align:middle;">IZBA</span>{% endif %}
</div>
{% if event.is_external and event.external_source %}
<div class="external-source">Źródło: {{ event.external_source }}</div>
{% endif %}
<div class="event-meta">
<span class="badge-type {{ 'external' if event.is_external else event.event_type }}">{{ 'Zewnętrzne' if event.is_external else pl_types.get(event.event_type, event.event_type) }}</span>
{% if event.time_start %}
<span>{{ event.time_start.strftime('%H:%M') }}{% if event.time_end %} - {{ event.time_end.strftime('%H:%M') }}{% endif %}</span>
{% endif %}
{% if event.location %}
<span>
{% if event.location_url %}
<a href="{{ event.location_url }}" target="_blank" style="color:var(--primary);text-decoration:none;">{{ event.location }}</a>
{% elif 'do ustalenia' not in event.location|lower and event.location != 'Online' %}
<a href="https://www.google.com/maps/search/{{ event.location|urlencode }}" target="_blank" style="color:var(--primary);text-decoration:none;">{{ event.location }}</a>
{% else %}
{{ event.location }}
{% endif %}
</span>
{% endif %}
{% if event.speaker_name %}
<span>Prelegent: {{ event.speaker_name }}</span>
{% endif %}
</div>
{% if event.description %}
<div class="event-description">{{ event.description|striptags|truncate(150, True) }}</div>
{% endif %}
</div>
<div class="event-actions">
{% if event.is_external %}
<span class="attendee-count">{{ event.attendee_count }} zainteresowanych</span>
{% if event.can_user_attend(current_user) %}
{% set is_attending = event.attendees|selectattr('user_id','equalto',current_user.id)|list %}
<button class="btn btn-sm rsvp-list-btn {{ 'rsvp-attending' if is_attending else 'rsvp-not-attending' }}" data-event-id="{{ event.id }}" data-attending="{{ 'true' if is_attending else 'false' }}" data-external="true" onclick="toggleListRSVP(this)" {{ 'onmouseenter=this.textContent="Nie interesuje" onmouseleave=this.textContent="Zainteresowany "'|safe if is_attending }}>
{{ 'Zainteresowany ✓' if is_attending else 'Zainteresowany' }}
</button>
{% endif %}
{% else %}
<span class="attendee-count">{{ event.attendee_count }} uczestników</span>
{% if event.can_user_attend(current_user) %}
{% set is_attending = event.attendees|selectattr('user_id','equalto',current_user.id)|list %}
<button class="btn btn-sm rsvp-list-btn {{ 'rsvp-attending' if is_attending else 'rsvp-not-attending' }}" data-event-id="{{ event.id }}" data-attending="{{ 'true' if is_attending else 'false' }}" data-external="false" onclick="toggleListRSVP(this)" {{ 'onmouseenter=this.textContent="Wypisz się" onmouseleave=this.textContent="Zapisano "'|safe if is_attending }}>
{{ 'Zapisano ✓' if is_attending else 'Zapisz się' }}
</button>
{% endif %}
{% endif %}
<a href="{{ url_for('calendar.calendar_event', event_id=event.id) }}" class="btn btn-outline btn-sm">Szczegóły</a>
</div>
</div>
{% endfor %}
{% else %}
<div class="empty-state">
<p>Brak nadchodzących wydarzeń</p>
</div>
{% endif %}
</div>
<!-- Przeszłe wydarzenia -->
{% if past_events %}
<div class="events-section">
<h2>Ostatnie wydarzenia</h2>
{% for event in past_events %}
<div class="event-card past">
<div class="event-date-box" style="background: var(--secondary);">
<div class="day">{{ event.event_date.day }}</div>
<div class="month">{{ pl_months.get(event.event_date.strftime('%b'), event.event_date.strftime('%b')) }}</div>
</div>
<div class="event-info">
<div class="event-title">
<a href="{{ url_for('calendar.calendar_event', event_id=event.id) }}">{{ event.title }}</a>
</div>
<div class="event-meta">
<span class="badge-type {{ event.event_type }}">{{ pl_types.get(event.event_type, event.event_type) }}</span>
{% if event.location %}
<span>{{ event.location }}</span>
{% endif %}
</div>
</div>
<div class="event-actions">
<span class="attendee-count">{{ event.attendee_count }} uczestników</span>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}
{% endblock %}
{% block extra_js %}
var csrfToken = '{{ csrf_token() }}';
async function toggleListRSVP(btn) {
var eventId = btn.dataset.eventId;
var isExt = btn.dataset.external === 'true';
btn.disabled = true;
try {
var resp = await fetch('/kalendarz/' + eventId + '/rsvp', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrfToken}
});
var data = await resp.json();
if (data.success) {
var addedText = isExt ? 'Zainteresowany ✓' : 'Zapisano ✓';
var removedText = isExt ? 'Zainteresowany' : 'Zapisz się';
var hoverText = isExt ? 'Nie interesuje' : 'Wypisz się';
var countLabel = isExt ? ' zainteresowanych' : ' uczestników';
if (data.action === 'added') {
btn.textContent = addedText;
btn.classList.remove('rsvp-not-attending');
btn.classList.add('rsvp-attending');
btn.dataset.attending = 'true';
btn.onmouseenter = function() { this.textContent = hoverText; };
btn.onmouseleave = function() { this.textContent = addedText; };
} else {
btn.textContent = removedText;
btn.classList.remove('rsvp-attending');
btn.classList.add('rsvp-not-attending');
btn.dataset.attending = 'false';
btn.onmouseenter = null;
btn.onmouseleave = null;
}
var countEl = btn.parentElement.querySelector('.attendee-count');
if (countEl) countEl.textContent = data.attendee_count + countLabel;
}
} catch(e) {}
btn.disabled = false;
}
/* Filter toggle for external events */
(function() {
var cb = document.getElementById('show-external');
var label = document.getElementById('external-filter-label');
var textEl = document.getElementById('external-filter-text');
var stored = localStorage.getItem('nordabiz_show_external');
if (stored === 'false') cb.checked = false;
var extItems = document.querySelectorAll('[data-external="true"]');
var count = extItems.length;
if (count > 0) {
label.style.display = '';
textEl.textContent = 'Pokaż zewnętrzne (' + count + ')';
}
function applyFilter() {
var show = cb.checked;
localStorage.setItem('nordabiz_show_external', show);
extItems.forEach(function(el) {
el.style.display = show ? '' : 'none';
});
}
cb.addEventListener('change', applyFilter);
applyFilter();
})();
var plMonths = ['','stycznia','lutego','marca','kwietnia','maja','czerwca','lipca','sierpnia','września','października','listopada','grudnia'];
function openDayModal(day, month, year) {
var modal = document.getElementById('dayModal');
var overlay = document.getElementById('dayModalOverlay');
var title = document.getElementById('dayModalTitle');
var eventsDiv = document.getElementById('dayModalEvents');
title.textContent = day + ' ' + plMonths[month] + ' ' + year;
// Find events for this day from DOM
var dayCell = document.querySelector('.calendar-day[data-day="' + day + '"].has-events');
if (!dayCell) return;
var events = dayCell.querySelectorAll('.calendar-event');
var html = '';
events.forEach(function(ev) {
var eventType = '';
['meeting','networking','webinar','other','external'].forEach(function(t) {
if (ev.classList.contains(t)) eventType = t;
});
var time = ev.dataset.eventTime || '';
var loc = ev.dataset.eventLocation || '';
var meta = [];
if (ev.dataset.eventType) meta.push(ev.dataset.eventType);
if (time) meta.push(time);
if (loc) meta.push(loc);
html += '<a href="' + ev.dataset.eventUrl + '" class="day-modal-event">' +
'<div class="day-modal-event-dot ' + eventType + '"></div>' +
'<div class="day-modal-event-info">' +
'<div class="day-modal-event-title">' + ev.dataset.eventTitle + '</div>' +
(meta.length ? '<div class="day-modal-event-meta">' + meta.join(' · ') + '</div>' : '') +
'</div>' +
'<span class="day-modal-event-arrow"></span>' +
'</a>';
});
eventsDiv.innerHTML = html;
overlay.classList.add('show');
modal.classList.add('show');
}
function closeDayModal() {
document.getElementById('dayModal').classList.remove('show');
document.getElementById('dayModalOverlay').classList.remove('show');
}
function showSubscribeModal() {
document.getElementById('subscribeModal').style.display = 'flex';
}
function copyIcalUrl() {
var input = document.getElementById('icalUrl');
input.select();
document.execCommand('copy');
var btn = document.getElementById('copyBtn');
btn.textContent = 'Skopiowano!';
setTimeout(function() { btn.textContent = 'Kopiuj'; }, 2000);
}
document.getElementById('subscribeModal').addEventListener('click', function(e) {
if (e.target === this) this.style.display = 'none';
});
{% endblock %}