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
Changed person linking to use User.id → /profil/<user_id> instead of requiring Person record (person_id). This makes 57 additional users linkable as green pill badges in event details and descriptions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
692 lines
23 KiB
HTML
Executable File
692 lines
23 KiB
HTML
Executable File
{% extends "base.html" %}
|
||
|
||
{% block title %}{{ event.title }} - Norda Biznes Partner{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.event-header {
|
||
margin-bottom: var(--spacing-xl);
|
||
}
|
||
|
||
.event-header h1 {
|
||
font-size: var(--font-size-3xl);
|
||
color: var(--text-primary);
|
||
margin-bottom: var(--spacing-sm);
|
||
}
|
||
|
||
.event-detail {
|
||
background: var(--surface);
|
||
border-radius: var(--radius-lg);
|
||
padding: var(--spacing-xl);
|
||
box-shadow: var(--shadow);
|
||
margin-bottom: var(--spacing-xl);
|
||
}
|
||
|
||
.event-info-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: var(--spacing-lg);
|
||
margin-bottom: var(--spacing-xl);
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
|
||
.info-item svg {
|
||
width: 20px;
|
||
height: 20px;
|
||
color: var(--primary);
|
||
flex-shrink: 0;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: var(--font-size-sm);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.info-value {
|
||
font-weight: 500;
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.event-description {
|
||
margin-bottom: var(--spacing-xl);
|
||
line-height: 1.7;
|
||
color: var(--text-primary);
|
||
font-size: 1.05rem;
|
||
}
|
||
|
||
.event-description p {
|
||
margin-bottom: var(--spacing-md);
|
||
}
|
||
|
||
.event-description p:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.event-description h3 {
|
||
font-size: var(--font-size-lg);
|
||
margin: var(--spacing-lg) 0 var(--spacing-sm);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.event-description ul {
|
||
margin: 0 0 var(--spacing-md) var(--spacing-lg);
|
||
padding: 0;
|
||
}
|
||
|
||
.event-description li {
|
||
margin-bottom: var(--spacing-xs);
|
||
}
|
||
|
||
.event-callout {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: var(--spacing-md);
|
||
padding: var(--spacing-lg);
|
||
background: linear-gradient(135deg, #f0fdf4, #ecfdf5);
|
||
border: 1px solid #bbf7d0;
|
||
border-radius: var(--radius-lg);
|
||
margin-top: var(--spacing-lg);
|
||
white-space: normal;
|
||
}
|
||
|
||
.event-callout svg {
|
||
width: 22px;
|
||
height: 22px;
|
||
color: #16a34a;
|
||
flex-shrink: 0;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.event-callout strong {
|
||
color: #15803d;
|
||
}
|
||
|
||
.event-callout small {
|
||
display: block;
|
||
margin-top: var(--spacing-xs);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.rsvp-section {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-lg);
|
||
padding: var(--spacing-lg);
|
||
background: var(--background);
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
.attendees-section {
|
||
background: var(--surface);
|
||
border-radius: var(--radius-lg);
|
||
padding: var(--spacing-xl);
|
||
box-shadow: var(--shadow);
|
||
}
|
||
|
||
.attendees-section h2 {
|
||
font-size: var(--font-size-xl);
|
||
margin-bottom: var(--spacing-lg);
|
||
}
|
||
|
||
.attendees-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
|
||
.attendee-badge {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-sm);
|
||
padding: var(--spacing-xs);
|
||
background: var(--background);
|
||
border-radius: var(--radius);
|
||
font-size: var(--font-size-sm);
|
||
}
|
||
|
||
/* Unverified person - blue */
|
||
.attendee-name {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--spacing-xs);
|
||
padding: var(--spacing-xs) var(--spacing-sm);
|
||
background: #dbeafe;
|
||
color: #1e40af;
|
||
border-radius: var(--radius);
|
||
font-weight: 500;
|
||
font-size: var(--font-size-sm);
|
||
text-decoration: none;
|
||
transition: var(--transition);
|
||
}
|
||
|
||
/* Verified person - green */
|
||
.attendee-name.verified {
|
||
background: #dcfce7;
|
||
color: #166534;
|
||
}
|
||
|
||
a.attendee-name:hover {
|
||
background: #bfdbfe;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
a.attendee-name.verified:hover {
|
||
background: #bbf7d0;
|
||
}
|
||
|
||
.attendee-name svg {
|
||
width: 14px;
|
||
height: 14px;
|
||
}
|
||
|
||
.attendee-company {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--spacing-xs);
|
||
padding: var(--spacing-xs) var(--spacing-sm);
|
||
background: #fee2e2;
|
||
color: #991b1b;
|
||
border-radius: var(--radius);
|
||
font-weight: 500;
|
||
font-size: var(--font-size-sm);
|
||
text-decoration: none;
|
||
transition: var(--transition);
|
||
}
|
||
|
||
.attendee-company:hover {
|
||
background: #fecaca;
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.attendee-company svg {
|
||
width: 14px;
|
||
height: 14px;
|
||
}
|
||
|
||
.back-link {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--spacing-xs);
|
||
color: var(--text-secondary);
|
||
text-decoration: none;
|
||
margin-bottom: var(--spacing-lg);
|
||
}
|
||
|
||
.back-link:hover {
|
||
color: var(--primary);
|
||
}
|
||
|
||
#rsvp-btn.attending {
|
||
background: var(--success);
|
||
}
|
||
|
||
.event-logo {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-md);
|
||
margin-bottom: var(--spacing-lg);
|
||
}
|
||
|
||
.event-logo img {
|
||
height: 48px;
|
||
width: auto;
|
||
}
|
||
|
||
/* Pill badge links — spójne z NordaGPT chat */
|
||
.person-link, .company-link {
|
||
display: inline-block;
|
||
padding: 2px 10px;
|
||
margin: 1px 2px;
|
||
border-radius: 12px;
|
||
text-decoration: none;
|
||
font-weight: 600;
|
||
font-size: 0.95em;
|
||
transition: var(--transition);
|
||
}
|
||
|
||
.person-link {
|
||
background: #ecfdf5;
|
||
color: #047857;
|
||
}
|
||
.person-link:hover {
|
||
background: #d1fae5;
|
||
color: #065f46;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 2px 4px rgba(4, 120, 87, 0.2);
|
||
}
|
||
|
||
.company-link {
|
||
background: #fff7ed;
|
||
color: #c2410c;
|
||
}
|
||
.company-link:hover {
|
||
background: #ffedd5;
|
||
color: #9a3412;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 2px 4px rgba(194, 65, 12, 0.2);
|
||
}
|
||
|
||
.speaker-link {
|
||
color: var(--primary);
|
||
text-decoration: none;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.speaker-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.calendar-add {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--spacing-md);
|
||
padding: var(--spacing-md) var(--spacing-lg);
|
||
background: linear-gradient(135deg, #f0f9ff, #eff6ff);
|
||
border: 1px solid #bfdbfe;
|
||
border-radius: var(--radius-lg);
|
||
margin-bottom: var(--spacing-xl);
|
||
}
|
||
|
||
.calendar-add-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
background: var(--primary);
|
||
border-radius: var(--radius);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.calendar-add-icon svg {
|
||
width: 20px;
|
||
height: 20px;
|
||
color: white;
|
||
}
|
||
|
||
.calendar-add-label {
|
||
font-size: var(--font-size-sm);
|
||
font-weight: 600;
|
||
color: #1e40af;
|
||
}
|
||
|
||
.calendar-add-buttons {
|
||
display: flex;
|
||
gap: var(--spacing-sm);
|
||
margin-left: auto;
|
||
}
|
||
|
||
.calendar-add-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 8px 16px;
|
||
border-radius: var(--radius);
|
||
font-size: var(--font-size-sm);
|
||
font-weight: 500;
|
||
text-decoration: none;
|
||
transition: var(--transition);
|
||
border: none;
|
||
cursor: pointer;
|
||
color: white;
|
||
}
|
||
|
||
.calendar-add-btn.google {
|
||
background: #4285f4;
|
||
}
|
||
|
||
.calendar-add-btn.google:hover {
|
||
background: #3367d6;
|
||
}
|
||
|
||
.calendar-add-btn.outlook {
|
||
background: #0078d4;
|
||
}
|
||
|
||
.calendar-add-btn.outlook:hover {
|
||
background: #106ebe;
|
||
}
|
||
|
||
.calendar-add-btn svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.calendar-add {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: var(--spacing-sm);
|
||
}
|
||
.calendar-add-buttons {
|
||
margin-left: 0;
|
||
}
|
||
.calendar-add-icon {
|
||
display: none;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<a href="{{ url_for('calendar.calendar_index') }}" class="back-link">
|
||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
||
</svg>
|
||
Powrót do kalendarza
|
||
</a>
|
||
|
||
<div class="event-header">
|
||
{% if event.is_featured %}
|
||
<div class="event-logo">
|
||
<img src="{{ url_for('static', filename='img/norda-logo.svg') }}" alt="Norda Biznes">
|
||
</div>
|
||
{% endif %}
|
||
<h1>{{ event.title }}
|
||
{% if event.access_level == 'admin_only' %}
|
||
<span style="display:inline-block;background:#ef4444;color:#fff;font-size:12px;padding:2px 8px;border-radius:4px;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:12px;padding:2px 8px;border-radius:4px;font-weight:600;vertical-align:middle;">IZBA</span>
|
||
{% endif %}
|
||
</h1>
|
||
</div>
|
||
|
||
<div class="event-detail">
|
||
<div class="event-info-grid">
|
||
<div class="info-item">
|
||
<svg 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"></rect>
|
||
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||
<line x1="8" y1="2" x2="8" y2="6"></line>
|
||
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||
</svg>
|
||
<div>
|
||
<div class="info-label">Data</div>
|
||
<div class="info-value">{{ event.event_date.strftime('%d.%m.%Y') }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% if event.time_start %}
|
||
<div class="info-item">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<circle cx="12" cy="12" r="10"></circle>
|
||
<polyline points="12 6 12 12 16 14"></polyline>
|
||
</svg>
|
||
<div>
|
||
<div class="info-label">Godzina</div>
|
||
<div class="info-value">{{ event.time_start.strftime('%H:%M') }}{% if event.time_end %} - {{ event.time_end.strftime('%H:%M') }}{% endif %}</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if event.location %}
|
||
<div class="info-item">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
||
<circle cx="12" cy="10" r="3"></circle>
|
||
</svg>
|
||
<div>
|
||
<div class="info-label">Miejsce</div>
|
||
<div class="info-value">
|
||
{% if event.location_url %}
|
||
<a href="{{ event.location_url }}" target="_blank" style="color:var(--primary);">{{ event.location }}</a>
|
||
{% elif event.location and event.location not in ['Do ustalenia', 'Online'] and 'do ustalenia' not in event.location|lower %}
|
||
<a href="https://www.google.com/maps/search/{{ event.location|urlencode }}" target="_blank" style="color:var(--primary);">{{ event.location }}</a>
|
||
{% else %}
|
||
{{ event.location }}
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if event.speaker_name %}
|
||
<div class="info-item">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||
<circle cx="12" cy="7" r="4"></circle>
|
||
</svg>
|
||
<div>
|
||
<div class="info-label">Prelegent</div>
|
||
<div class="info-value">
|
||
{% if speaker_user_id %}
|
||
<a href="{{ url_for('user_profile', user_id=speaker_user_id) }}" class="person-link">{{ event.speaker_name }}</a>
|
||
{% elif speaker_company_slug %}
|
||
<a href="{{ url_for('company_detail_by_slug', slug=speaker_company_slug) }}" class="company-link">{{ event.speaker_name }}</a>
|
||
{% else %}
|
||
{{ event.speaker_name }}
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if event.time_start and not event.is_past %}
|
||
<div class="calendar-add">
|
||
<div class="calendar-add-icon">
|
||
<svg 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"></rect>
|
||
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||
<line x1="8" y1="2" x2="8" y2="6"></line>
|
||
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||
</svg>
|
||
</div>
|
||
<div class="calendar-add-label">Dodaj do kalendarza</div>
|
||
<div class="calendar-add-buttons">
|
||
<a href="#" class="calendar-add-btn google" onclick="addToGoogleCalendar(); return false;">
|
||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.94-.49-7-3.85-7-7.93s3.05-7.44 7-7.93v2.02C8.16 6.57 6 9.03 6 12s2.16 5.43 5 5.91v2.02zm2 0V17.9c2.84-.48 5-2.94 5-5.9s-2.16-5.43-5-5.91V4.07c3.94.49 7 3.85 7 7.93s-3.06 7.44-7 7.93z"/></svg>
|
||
Google
|
||
</a>
|
||
<a href="#" class="calendar-add-btn outlook" onclick="downloadICS(); return false;">
|
||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm0-12H5V6h14v2z"/></svg>
|
||
Outlook / ICS
|
||
</a>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if event.image_url %}
|
||
<div style="margin-bottom: var(--spacing-xl); border-radius: var(--radius-lg); overflow: hidden;">
|
||
<img src="{{ event.image_url }}" alt="{{ event.title }}" style="width: 100%; height: auto; display: block;">
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if enriched_description %}
|
||
<div class="event-description">
|
||
{{ enriched_description }}
|
||
</div>
|
||
{% elif event.description %}
|
||
<div class="event-description">
|
||
{{ event.description|safe }}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if not event.is_past %}
|
||
{% if event.can_user_attend(current_user) %}
|
||
<div class="rsvp-section">
|
||
<div>
|
||
<strong>Chcesz wziąć udział?</strong>
|
||
<p class="text-muted" style="margin: 0;">{{ event.attendee_count }} osób już się zapisało{% if event.max_attendees %} (limit: {{ event.max_attendees }}){% endif %}</p>
|
||
</div>
|
||
<button id="rsvp-btn" class="btn {% if user_attending %}btn-secondary attending{% else %}btn-primary{% endif %}" onclick="toggleRSVP()">
|
||
{% if user_attending %}Wypisz się{% else %}Wezmę udział{% endif %}
|
||
</button>
|
||
</div>
|
||
{% elif event.access_level == 'rada_only' %}
|
||
<div class="rsvp-section" style="background: #fef3c7; border: 1px solid #fde68a;">
|
||
<svg width="24" height="24" fill="none" stroke="#92400e" stroke-width="2" viewBox="0 0 24 24" style="flex-shrink: 0;">
|
||
<path d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
||
</svg>
|
||
<div>
|
||
<strong style="color: #92400e;">Wydarzenie tylko dla Rady Izby</strong>
|
||
<p class="text-muted" style="margin: 0; color: #a16207;">Zapisy są dostępne wyłącznie dla członków Rady Izby NORDA.</p>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% else %}
|
||
<div class="rsvp-section" style="background: var(--border);">
|
||
<p class="text-muted" style="margin: 0;">To wydarzenie już się odbyło.{% if event.can_user_see_attendees(current_user) %} {{ event.attendee_count }} osób brało udział.{% endif %}</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if event.attendees and event.can_user_see_attendees(current_user) %}
|
||
<div class="attendees-section">
|
||
<h2>Uczestnicy ({{ event.attendee_count }})</h2>
|
||
<div class="attendees-list">
|
||
{% for attendee in event.attendees|sort(attribute='user.name') %}
|
||
<div class="attendee-badge">
|
||
{# Person badge - always clickable: person profile if person_id, user profile otherwise #}
|
||
{% if attendee.user.person_id %}
|
||
<a href="{{ url_for('person_detail', person_id=attendee.user.person_id) }}" class="attendee-name verified">
|
||
{% else %}
|
||
<a href="{{ url_for('public.user_profile', user_id=attendee.user.id) }}" class="attendee-name verified">
|
||
{% endif %}
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||
<circle cx="12" cy="7" r="4"></circle>
|
||
</svg>
|
||
{{ attendee.user.name or attendee.user.email.split('@')[0] }}
|
||
</a>
|
||
|
||
{# Company badge - always link to company profile #}
|
||
{% if attendee.user.company %}
|
||
<a href="{{ url_for('company_detail_by_slug', slug=attendee.user.company.slug) }}" class="attendee-company">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M19 21V5a2 2 0 0 0-2-2H7a2 2 0 0 0-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v5m-4 0h4"></path>
|
||
</svg>
|
||
{{ attendee.user.company.name }}
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div id="toastContainer" style="position: fixed; top: 80px; right: 20px; z-index: 1100; display: flex; flex-direction: column; gap: 10px;"></div>
|
||
|
||
<style>
|
||
.toast { padding: 12px 20px; border-radius: var(--radius); background: var(--surface); border-left: 4px solid var(--primary); box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px; animation: toastIn 0.3s ease; }
|
||
.toast.success { border-left-color: var(--success); }
|
||
.toast.error { border-left-color: var(--error); }
|
||
@keyframes toastIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
||
@keyframes toastOut { from { opacity: 1; } to { opacity: 0; } }
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
const csrfToken = '{{ csrf_token() }}';
|
||
|
||
function showToast(message, type = 'info', duration = 4000) {
|
||
const container = document.getElementById('toastContainer');
|
||
const icons = { success: '✓', error: '✕', warning: '⚠', info: 'ℹ' };
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast ${type}`;
|
||
toast.innerHTML = `<span style="font-size:1.2em">${icons[type]||'ℹ'}</span><span>${message}</span>`;
|
||
container.appendChild(toast);
|
||
setTimeout(() => { toast.style.animation = 'toastOut 0.3s ease forwards'; setTimeout(() => toast.remove(), 300); }, duration);
|
||
}
|
||
|
||
async function toggleRSVP() {
|
||
const btn = document.getElementById('rsvp-btn');
|
||
btn.disabled = true;
|
||
|
||
try {
|
||
const response = await fetch('{{ url_for("calendar.calendar_rsvp", event_id=event.id) }}', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': csrfToken
|
||
}
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
if (data.action === 'added') {
|
||
btn.textContent = 'Wypisz się';
|
||
btn.classList.remove('btn-primary');
|
||
btn.classList.add('btn-secondary', 'attending');
|
||
showToast('Zapisano na wydarzenie!', 'success');
|
||
} else {
|
||
btn.textContent = 'Wezmę udział';
|
||
btn.classList.remove('btn-secondary', 'attending');
|
||
btn.classList.add('btn-primary');
|
||
showToast('Wypisano z wydarzenia', 'info');
|
||
}
|
||
// Refresh page to update attendees list
|
||
setTimeout(() => location.reload(), 1000);
|
||
} else {
|
||
showToast(data.error || 'Wystąpił błąd', 'error');
|
||
}
|
||
} catch (error) {
|
||
showToast('Błąd połączenia', 'error');
|
||
}
|
||
|
||
btn.disabled = false;
|
||
}
|
||
|
||
/* --- Add to Calendar functions --- */
|
||
const _evt = {
|
||
title: {{ event.title|tojson }},
|
||
date: '{{ event.event_date.strftime("%Y%m%d") }}',
|
||
start: '{{ event.time_start.strftime("%H%M") if event.time_start else "0000" }}',
|
||
end: '{{ event.time_end.strftime("%H%M") if event.time_end else "" }}',
|
||
location: {{ (event.location or '')|tojson }},
|
||
url: window.location.href,
|
||
};
|
||
|
||
function addToGoogleCalendar() {
|
||
const start = _evt.date + 'T' + _evt.start + '00';
|
||
const end = _evt.end ? (_evt.date + 'T' + _evt.end + '00') : '';
|
||
const params = new URLSearchParams({
|
||
action: 'TEMPLATE',
|
||
text: _evt.title,
|
||
dates: start + '/' + (end || start),
|
||
location: _evt.location,
|
||
details: 'Szczegóły: ' + _evt.url,
|
||
ctz: 'Europe/Warsaw',
|
||
});
|
||
window.open('https://calendar.google.com/calendar/render?' + params, '_blank');
|
||
}
|
||
|
||
function downloadICS() {
|
||
const start = _evt.date + 'T' + _evt.start + '00';
|
||
const end = _evt.end ? (_evt.date + 'T' + _evt.end + '00') : (_evt.date + 'T' + _evt.start + '00');
|
||
const uid = 'nordabiz-event-{{ event.id }}@nordabiznes.pl';
|
||
const now = new Date().toISOString().replace(/[-:]/g,'').split('.')[0] + 'Z';
|
||
const speaker = {{ (event.speaker_name or '')|tojson }};
|
||
const desc = (speaker ? 'Prowadzący: ' + speaker + '\\n' : '') + 'Szczegóły: ' + _evt.url;
|
||
const ics = [
|
||
'BEGIN:VCALENDAR',
|
||
'VERSION:2.0',
|
||
'PRODID:-//NordaBiznes//PL',
|
||
'BEGIN:VEVENT',
|
||
'UID:' + uid,
|
||
'DTSTAMP:' + now,
|
||
'DTSTART;TZID=Europe/Warsaw:' + start,
|
||
'DTEND;TZID=Europe/Warsaw:' + end,
|
||
'SUMMARY:' + _evt.title,
|
||
'LOCATION:' + _evt.location,
|
||
'DESCRIPTION:' + desc,
|
||
'URL:' + _evt.url,
|
||
'ORGANIZER;CN=Norda Biznes:mailto:biuro@norda-biznes.info',
|
||
'END:VEVENT',
|
||
'END:VCALENDAR',
|
||
].join('\r\n');
|
||
const blob = new Blob([ics], { type: 'text/calendar;charset=utf-8' });
|
||
const a = document.createElement('a');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.download = _evt.title.substring(0, 50).replace(/[^a-zA-Z0-9ąćęłńóśźżĄĆĘŁŃÓŚŹŻ ]/g, '') + '.ics';
|
||
a.click();
|
||
URL.revokeObjectURL(a.href);
|
||
}
|
||
{% endblock %}
|