diff --git a/blueprints/admin/routes.py b/blueprints/admin/routes.py index f215fc9..3a1cd74 100644 --- a/blueprints/admin/routes.py +++ b/blueprints/admin/routes.py @@ -943,6 +943,8 @@ def admin_calendar_new(): if event_type == 'rada' and access_level != 'rada_only': access_level = 'rada_only' + is_external = request.form.get('is_external') == 'on' + event = NordaEvent( title=request.form.get('title', '').strip(), description=sanitize_html(request.form.get('description', '').strip()), @@ -951,10 +953,13 @@ def admin_calendar_new(): time_end=request.form.get('time_end') or None, location=request.form.get('location', '').strip() or None, event_type=event_type, - max_attendees=request.form.get('max_attendees', type=int) or None, + max_attendees=None if is_external else (request.form.get('max_attendees', type=int) or None), access_level=access_level, created_by=current_user.id, - source='manual' + source='manual', + is_external=is_external, + external_url=request.form.get('external_url', '').strip() or None if is_external else None, + external_source=request.form.get('external_source', '').strip() or None if is_external else None, ) # Handle file attachment diff --git a/blueprints/community/calendar/routes.py b/blueprints/community/calendar/routes.py index 0d1aa34..b5fa782 100644 --- a/blueprints/community/calendar/routes.py +++ b/blueprints/community/calendar/routes.py @@ -328,19 +328,22 @@ def rsvp(event_id): EventAttendee.user_id == current_user.id ).first() + is_ext = getattr(event, 'is_external', False) or False + msg_added = 'Oznaczono jako zainteresowany' if is_ext else 'Zapisano na wydarzenie' + msg_removed = 'Usunięto zainteresowanie' if is_ext else 'Wypisano z wydarzenia' + if existing: - # Wypisz db.delete(existing) db.commit() return jsonify({ 'success': True, 'action': 'removed', - 'message': 'Wypisano z wydarzenia', + 'message': msg_removed, 'attendee_count': event.attendee_count }) else: - # Zapisz - if event.max_attendees and event.attendee_count >= event.max_attendees: + # Skip max_attendees check for external events + if not is_ext and event.max_attendees and event.attendee_count >= event.max_attendees: return jsonify({'success': False, 'error': 'Brak wolnych miejsc'}), 400 attendee = EventAttendee( @@ -353,7 +356,7 @@ def rsvp(event_id): return jsonify({ 'success': True, 'action': 'added', - 'message': 'Zapisano na wydarzenie', + 'message': msg_added, 'attendee_count': event.attendee_count }) finally: diff --git a/database.py b/database.py index 68488da..4598dbb 100644 --- a/database.py +++ b/database.py @@ -2174,6 +2174,11 @@ class NordaEvent(Base): organizer_name = Column(String(255), default='Norda Biznes') organizer_email = Column(String(255), default='biuro@norda-biznes.info') + # External event (ARP, KIG, etc.) + is_external = Column(Boolean, default=False) + external_url = Column(String(1000)) # Registration link at external organizer + external_source = Column(String(255)) # Source name (e.g. "Agencja Rozwoju Pomorza") + # Attachment attachment_filename = Column(String(255)) # Original filename attachment_path = Column(String(1000)) # Server path (static/uploads/events/...) diff --git a/database/migrations/086_external_events.sql b/database/migrations/086_external_events.sql new file mode 100644 index 0000000..a2b248f --- /dev/null +++ b/database/migrations/086_external_events.sql @@ -0,0 +1,9 @@ +-- Migration 086: Add external event fields to norda_events +-- For KIG integration: external events from ARP, KIG, partner organizations + +ALTER TABLE norda_events ADD COLUMN IF NOT EXISTS is_external BOOLEAN DEFAULT FALSE; +ALTER TABLE norda_events ADD COLUMN IF NOT EXISTS external_url VARCHAR(1000); +ALTER TABLE norda_events ADD COLUMN IF NOT EXISTS external_source VARCHAR(255); + +-- Grant permissions +GRANT ALL ON TABLE norda_events TO nordabiz_app; diff --git a/templates/calendar/admin_new.html b/templates/calendar/admin_new.html index 9791f35..3cd3545 100755 --- a/templates/calendar/admin_new.html +++ b/templates/calendar/admin_new.html @@ -106,6 +106,27 @@
+
+ +
Zaznacz dla wydarzeń organizowanych przez podmioty zewnętrzne (ARP, KIG, urzędy). Użytkownicy zobaczą przycisk "Jestem zainteresowany" zamiast "Zapisz się".
+
+ + +
@@ -197,3 +218,21 @@
{% endblock %} + +{% block extra_js %} +(function() { + const cb = document.getElementById('is_external'); + const extFields = document.getElementById('external-fields'); + const maxAtt = document.getElementById('max_attendees'); + const maxAttGroup = maxAtt ? maxAtt.closest('.form-group') : null; + + function toggle() { + const isExt = cb.checked; + extFields.style.display = isExt ? 'block' : 'none'; + if (maxAttGroup) maxAttGroup.style.display = isExt ? 'none' : 'block'; + } + + cb.addEventListener('change', toggle); + toggle(); +})(); +{% endblock %} diff --git a/templates/calendar/event.html b/templates/calendar/event.html index f2dc5ea..3412edf 100755 --- a/templates/calendar/event.html +++ b/templates/calendar/event.html @@ -383,13 +383,20 @@
- {% if event.is_featured %} + {% if event.is_external %} +
+ + Wydarzenie zewnętrzne{% if event.external_source %} · {{ event.external_source }}{% endif %} +
+ {% elif event.is_featured %} {% endif %}

{{ event.title }} - {% if event.access_level == 'admin_only' %} + {% if event.is_external %} + ZEWNĘTRZNE + {% elif event.access_level == 'admin_only' %} UKRYTE {% elif event.access_level == 'rada_only' %} IZBA @@ -492,6 +499,18 @@

{% 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 }} @@ -526,6 +545,15 @@ {% if not event.is_past %} {% if event.can_user_attend(current_user) %}
+ {% if event.is_external %} +
+ Interesuje Cię to wydarzenie? +

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

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

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

@@ -533,6 +561,7 @@ + {% endif %}
{% elif event.access_level == 'rada_only' %}
@@ -554,7 +583,7 @@ {% if event.attendees and event.can_user_see_attendees(current_user) %}
-

Uczestnicy ({{ event.attendee_count }})

+

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

{% for attendee in event.attendees|sort(attribute='user.name') %}
@@ -624,17 +653,18 @@ async function toggleRSVP() { }); const data = await response.json(); + const isExt = {{ 'true' if event.is_external else 'false' }}; if (data.success) { if (data.action === 'added') { - btn.textContent = 'Wypisz się'; + btn.textContent = isExt ? 'Nie interesuje mnie' : 'Wypisz się'; btn.classList.remove('btn-primary'); btn.classList.add('btn-secondary', 'attending'); - showToast('Zapisano na wydarzenie!', 'success'); + showToast(isExt ? 'Oznaczono jako zainteresowany!' : 'Zapisano na wydarzenie!', 'success'); } else { - btn.textContent = 'Wezmę udział'; + btn.textContent = isExt ? 'Jestem zainteresowany' : 'Wezmę udział'; btn.classList.remove('btn-secondary', 'attending'); btn.classList.add('btn-primary'); - showToast('Wypisano z wydarzenia', 'info'); + showToast(isExt ? 'Usunięto zainteresowanie' : 'Wypisano z wydarzenia', 'info'); } // Refresh page to update attendees list setTimeout(() => location.reload(), 1000); diff --git a/templates/calendar/index.html b/templates/calendar/index.html index d7732a5..b9e7f1c 100755 --- a/templates/calendar/index.html +++ b/templates/calendar/index.html @@ -191,6 +191,12 @@ color: #7c3aed; } + .calendar-event.external { + background: #f1f5f9; + color: #475569; + border-left: 2px solid #94a3b8; + } + /* Widok listy - istniejące style */ .events-section { margin-bottom: var(--spacing-2xl); @@ -320,6 +326,34 @@ .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: #f1f5f9; color: #475569; } + + .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 { @@ -398,6 +432,11 @@
{% endif %} + + {% if current_user.can_access_admin_panel() %} Zarządzaj {% endif %} @@ -428,8 +467,9 @@ {% if day in events_by_day %} {% for event in events_by_day[day] %} + class="calendar-event {{ 'external' if event.is_external else event.event_type }}" + data-external="{{ 'true' if event.is_external else 'false' }}" + title="{{ event.title }}{% if event.time_start %} - {{ event.time_start.strftime('%H:%M') }}{% endif %}{% if event.is_external %} ({{ event.external_source }}){% endif %}"> {% if event.time_start %}{{ event.time_start.strftime('%H:%M') }} {% endif %}{{ event.title[:18] }}{% if event.title|length > 18 %}...{% endif %} {% endfor %} @@ -447,6 +487,7 @@ Networking Webinar Inne + Zewnętrzne
{% else %} @@ -458,17 +499,20 @@ {% if upcoming_events %} {% for event in upcoming_events %} -