{% extends "base.html" %} {% block title %}{{ event.title }} - Norda Biznes Partner{% endblock %} {% block extra_css %} {% endblock %} {% block content %} Powrót do kalendarza {% if current_user.is_authenticated and current_user.can_access_admin_panel() %}
Edytuj wydarzenie {% if event.is_paid %} Zarządzaj płatnościami {% endif %}
{% endif %}
{% if event.is_external %}
Wydarzenie zewnętrzne{% if event.external_source %} · {{ event.external_source }}{% endif %}
{% elif event.is_featured %} {% endif %}

{{ event.title }} {% if event.is_external %} ZEWNĘTRZNE {% elif event.access_level == 'admin_only' %} UKRYTE {% elif event.access_level == 'rada_only' %} IZBA {% endif %}

{% if event.is_multi_day %}Termin{% else %}Data{% endif %}
{{ event.date_range_display }}
{% if event.time_start %}
Godzina
{{ event.time_start.strftime('%H:%M') }}{% if event.time_end %} - {{ event.time_end.strftime('%H:%M') }}{% endif %}
{% endif %} {% if event.location %}
Miejsce
{% if event.location_url %} {{ event.location }} {% elif event.location and event.location not in ['Do ustalenia', 'Online'] and 'do ustalenia' not in event.location|lower %} {{ event.location }} {% else %} {{ event.location }} {% endif %}
{% endif %} {% if event.speaker_name %}
Prelegent
{% if speaker_user_id %} {{ event.speaker_name }} {% elif speaker_company_slug %} {{ event.speaker_name }} {% else %} {{ event.speaker_name }} {% endif %}
{% endif %}
{% if event.time_start and not event.is_past %}
Dodaj do kalendarza
{% endif %} {% if event.is_external and event.external_url %}
Rejestracja u organizatora
Przejdź do rejestracji → {% if event.external_source %}
{{ event.external_source }}
{% endif %}
{% endif %} {% if event.image_url %}
{{ event.title }}
{% endif %} {% if enriched_description %}
{{ enriched_description }}
{% elif event.description %}
{{ event.description|safe }}
{% endif %} {% if event.attachment_filename %}
Załącznik
{{ event.attachment_filename }}
{% endif %} {% if not event.is_past %} {% if event.can_user_attend(current_user) %}
{% if event.is_external %}
Interesuje Cię to wydarzenie?

{{ event.total_attendee_count }} osób zainteresowanych z Izby

{% else %}
Chcesz wziąć udział?

{{ event.total_attendee_count }} osób już się zapisało{% if event.max_attendees %} (limit: {{ event.max_attendees }}){% endif %}

{% if event.is_paid and not user_attending %}

Wydarzenie płatne: {{ "%.0f"|format(event.price_member) }} zł (członkowie) / {{ "%.0f"|format(event.price_guest) }} zł (goście)

{% endif %}
{% endif %} {% if event.is_paid and user_attending %} {% set my_amount = (user_attending.payment_amount or 0)|float %} {% set guests_amount = user_guests|map(attribute='payment_amount')|select('ne', none)|map('float')|sum if user_guests else 0 %} {% set total_to_pay = my_amount + guests_amount %}
{% if user_attending.payment_status == 'paid' and not user_guests %} ✓ Płatność potwierdzona {% elif user_attending.payment_status == 'exempt' and not user_guests %} Zwolniony z opłaty {% else %}
Do wpłaty łącznie: {{ "%.0f"|format(total_to_pay) }} zł
Ty: {{ "%.0f"|format(my_amount) }} zł {% if user_guests %} + {{ user_guests|length }} {{ 'osoba' if user_guests|length == 1 else 'osoby' if user_guests|length < 5 else 'osób' }}: {{ "%.0f"|format(guests_amount) }} zł {% endif %}
{% endif %}
{% endif %}
{# --- Guest management section --- #} {% if not event.is_external %}
{% if user_guests %}

Twoi goście ({{ user_guests|length }}/5):

{% for guest in user_guests %}
{{ guest.display_name }}{% if guest.organization %} ({{ guest.organization }}){% endif %}
{% endfor %} {% endif %}
{% if user_guests|length < 5 %} {% endif %}
{% endif %} {% elif event.access_level == 'rada_only' %}
Wydarzenie tylko dla Rady Izby

Zapisy są dostępne wyłącznie dla członków Rady Izby NORDA.

{% endif %} {% else %}

To wydarzenie już się odbyło.{% if event.can_user_see_attendees(current_user) %} {{ event.total_attendee_count }} osób brało udział.{% endif %}

{% endif %}
{% if (event.attendees or event.guests) and event.can_user_see_attendees(current_user) %}

{{ 'Zainteresowani' if event.is_external else 'Uczestnicy' }} ({{ event.total_attendee_count }})

{# --- Regular attendees with their guests --- #} {% set ns = namespace(shown_hosts=[]) %} {% for attendee in event.attendees|sort(attribute='user.name') %} {# Guests of this attendee #} {% for guest in event.guests if guest.host_user_id == attendee.user.id %}
gość: {{ guest.display_name }}{% if guest.organization %} ({{ guest.organization }}){% endif %}
{% endfor %} {% if ns.shown_hosts.append(attendee.user.id) %}{% endif %} {% endfor %} {# --- Hosts who are NOT attending but have guests --- #} {% for guest in event.guests %} {% if guest.host_user_id not in ns.shown_hosts %} {% if ns.shown_hosts.append(guest.host_user_id) %}{% endif %}
{{ guest.host.name or 'Użytkownik' }} (nie uczestniczy)
{% for g in event.guests if g.host_user_id == guest.host_user_id %}
gość: {{ g.display_name }}{% if g.organization %} ({{ g.organization }}){% endif %}
{% endfor %} {% endif %} {% endfor %}
{% endif %}
{% 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 = `${icons[type]||'ℹ'}${message}`; 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(); const isExt = {{ 'true' if event.is_external else 'false' }}; if (data.success) { if (data.action === 'added') { btn.textContent = isExt ? 'Nie interesuje mnie' : 'Wypisz się'; btn.classList.remove('btn-primary'); btn.classList.add('btn-secondary', 'attending'); showToast(isExt ? 'Oznaczono jako zainteresowany!' : 'Zapisano na wydarzenie!', 'success'); } else { btn.textContent = isExt ? 'Zainteresowany' : 'Wezmę udział'; btn.classList.remove('btn-secondary', 'attending'); btn.classList.add('btn-primary'); showToast(isExt ? 'Usunięto zainteresowanie' : '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; } /* --- Guest management --- */ let colleaguesLoaded = false; function toggleGuestForm() { const form = document.getElementById('guest-form'); const btn = document.getElementById('add-guest-btn'); if (form.style.display === 'none') { document.getElementById('guest-edit-id').value = ''; document.getElementById('guest-first-name').value = ''; document.getElementById('guest-last-name').value = ''; document.getElementById('guest-org').value = ''; document.getElementById('guest-submit-btn').textContent = 'Dodaj'; document.getElementById('guest-form-error').style.display = 'none'; // Reset guest type to member if paid event with company const typeSelector = document.getElementById('guest-type-selector'); if (typeSelector) { selectGuestType('member'); loadColleagues(); } const cs = document.getElementById('colleague-select'); if (cs) cs.value = ''; form.style.display = 'block'; btn.style.display = 'none'; document.getElementById('guest-first-name').focus(); } else { cancelGuestForm(); } } function selectGuestType(type) { document.getElementById('guest-type').value = type; document.querySelectorAll('.guest-type-btn').forEach(b => { b.classList.toggle('active', b.dataset.type === type); }); const picker = document.getElementById('colleague-picker'); if (picker) { picker.style.display = type === 'member' ? 'block' : 'none'; } } async function loadColleagues() { if (colleaguesLoaded) return; const select = document.getElementById('colleague-select'); if (!select) return; try { const resp = await fetch('/kalendarz/{{ event.id }}/company-colleagues'); const data = await resp.json(); data.forEach(c => { const opt = document.createElement('option'); opt.value = JSON.stringify(c); opt.textContent = c.name + (c.already_registered ? ' (już zapisany/a)' : ''); if (c.already_registered) opt.disabled = true; select.appendChild(opt); }); colleaguesLoaded = true; } catch(e) {} } function onColleagueSelect() { const select = document.getElementById('colleague-select'); if (!select.value) return; try { const c = JSON.parse(select.value); document.getElementById('guest-first-name').value = c.first_name || ''; document.getElementById('guest-last-name').value = c.last_name || ''; document.getElementById('guest-org').value = '{{ (active_company.name if active_company else (current_user.company.name if current_user.company else ""))|e }}'; } catch(e) {} } function cancelGuestForm() { document.getElementById('guest-form').style.display = 'none'; const btn = document.getElementById('add-guest-btn'); if (btn) btn.style.display = ''; } function editGuest(guestId, firstName, lastName, org) { document.getElementById('guest-edit-id').value = guestId; document.getElementById('guest-first-name').value = firstName; document.getElementById('guest-last-name').value = lastName; document.getElementById('guest-org').value = org; document.getElementById('guest-submit-btn').textContent = 'Zapisz'; document.getElementById('guest-form-error').style.display = 'none'; document.getElementById('guest-form').style.display = 'block'; const btn = document.getElementById('add-guest-btn'); if (btn) btn.style.display = 'none'; document.getElementById('guest-first-name').focus(); } async function submitGuest() { const editId = document.getElementById('guest-edit-id').value; const firstName = document.getElementById('guest-first-name').value.trim(); const lastName = document.getElementById('guest-last-name').value.trim(); const org = document.getElementById('guest-org').value.trim(); const errEl = document.getElementById('guest-form-error'); if (!firstName && !lastName && !org) { errEl.textContent = 'Podaj przynajmniej imię, nazwisko lub firmę'; errEl.style.display = 'block'; return; } errEl.style.display = 'none'; const eventId = {{ event.id }}; const url = editId ? `/kalendarz/${eventId}/guests/${editId}` : `/kalendarz/${eventId}/guests`; const method = editId ? 'PATCH' : 'POST'; try { const resp = await fetch(url, { method, headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ first_name: firstName, last_name: lastName, organization: org, guest_type: document.getElementById('guest-type').value || 'external' }) }); const data = await resp.json(); if (data.success) { showToast(editId ? 'Dane gościa zaktualizowane' : 'Dodano osobę towarzyszącą', 'success'); setTimeout(() => location.reload(), 800); } else { errEl.textContent = data.error || 'Wystąpił błąd'; errEl.style.display = 'block'; } } catch (e) { errEl.textContent = 'Błąd połączenia'; errEl.style.display = 'block'; } } async function deleteGuest(guestId) { nordaConfirm('Czy na pewno chcesz usunąć tę osobę towarzyszącą?', function() { doDeleteGuest(guestId); }); } async function doDeleteGuest(guestId) { try { const resp = await fetch(`/kalendarz/{{ event.id }}/guests/${guestId}`, { method: 'DELETE', headers: { 'X-CSRFToken': csrfToken } }); const data = await resp.json(); if (data.success) { showToast('Usunięto osobę towarzyszącą', 'info'); setTimeout(() => location.reload(), 800); } else { showToast(data.error || 'Wystąpił błąd', 'error'); } } catch (e) { showToast('Błąd połączenia', 'error'); } } /* --- Add to Calendar functions --- */ function stripHtml(html) { const tmp = document.createElement('div'); tmp.innerHTML = html; return tmp.textContent || tmp.innerText || ''; } function foldIcsLine(line) { // ICS spec: lines max 75 octets, continuation lines start with space const parts = []; while (line.length > 75) { parts.push(line.substring(0, 75)); line = ' ' + line.substring(75); } parts.push(line); return parts.join('\r\n'); } const _evt = { title: {{ event.title|tojson }}, date: '{{ event.event_date.strftime("%Y%m%d") }}', dateEnd: '{{ event.event_date_end.strftime("%Y%m%d") if event.event_date_end else "" }}', 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 }}, speaker: {{ (event.speaker_name or '')|tojson }}, description: {{ (event.description or '')|tojson }}, organizerName: {{ (event.external_source if event.is_external and event.external_source else event.organizer_name or 'Norda Biznes')|tojson }}, organizerEmail: {{ (event.organizer_email or 'biuro@norda-biznes.info')|tojson }}, url: window.location.href, }; function addToGoogleCalendar() { const start = _evt.date + 'T' + _evt.start + '00'; const endDate = _evt.dateEnd || _evt.date; const end = _evt.end ? (endDate + 'T' + _evt.end + '00') : ''; const details = [ _evt.speaker ? 'Prowadzący: ' + _evt.speaker : '', _evt.description, '', 'Szczegóły: ' + _evt.url, ].filter(Boolean).join('\n'); const params = new URLSearchParams({ action: 'TEMPLATE', text: _evt.title, dates: start + '/' + (end || start), location: _evt.location, details: details, ctz: 'Europe/Warsaw', }); window.open('https://calendar.google.com/calendar/render?' + params, '_blank'); } function downloadICS() { const start = _evt.date + 'T' + _evt.start + '00'; const endDate = _evt.dateEnd || _evt.date; const end = _evt.end ? (endDate + 'T' + _evt.end + '00') : (endDate + 'T' + _evt.start + '00'); const uid = 'nordabiz-event-{{ event.id }}@nordabiznes.pl'; const now = new Date().toISOString().replace(/[-:]/g,'').split('.')[0] + 'Z'; const plainDesc = stripHtml(_evt.description); const descParts = [ _evt.speaker ? 'Prowadzący: ' + _evt.speaker : '', plainDesc, '', 'Szczegóły: ' + _evt.url, ].filter(Boolean); const desc = descParts.join('\\n'); const lines = [ '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=' + _evt.organizerName + ':mailto:' + _evt.organizerEmail, 'BEGIN:VALARM', 'TRIGGER:-P1D', 'ACTION:DISPLAY', 'DESCRIPTION:Jutro: ' + _evt.title, 'END:VALARM', 'END:VEVENT', 'END:VCALENDAR', ]; const ics = lines.map(foldIcsLine).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 %}