feat: add event edit functionality for OFFICE_MANAGER+
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
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
- Route GET/POST /admin/kalendarz/<id>/edytuj - Reuses admin_new.html template with pre-filled fields in edit mode - Edit button on event detail page and admin calendar list - Supports all fields including paid event settings and attachments Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
de6c429c4b
commit
84934621ef
@ -1291,6 +1291,70 @@ def admin_calendar_new():
|
||||
return render_template('calendar/admin_new.html', event=None)
|
||||
|
||||
|
||||
@bp.route('/kalendarz/<int:event_id>/edytuj', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
def admin_calendar_edit(event_id):
|
||||
"""Edytuj istniejące wydarzenie"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
event = db.query(NordaEvent).filter(NordaEvent.id == event_id).first()
|
||||
if not event:
|
||||
flash('Wydarzenie nie istnieje.', 'error')
|
||||
return redirect(url_for('.admin_calendar'))
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
access_level = request.form.get('access_level', 'members_only')
|
||||
event_type = request.form.get('event_type', 'meeting')
|
||||
|
||||
if event_type == 'rada' and access_level != 'rada_only':
|
||||
access_level = 'rada_only'
|
||||
|
||||
is_external = request.form.get('is_external') == 'on'
|
||||
is_paid = request.form.get('is_paid') == 'on'
|
||||
|
||||
event.title = request.form.get('title', '').strip()
|
||||
event.description = sanitize_html(request.form.get('description', '').strip())
|
||||
event.event_date = datetime.strptime(request.form.get('event_date'), '%Y-%m-%d').date()
|
||||
event.time_start = request.form.get('time_start') or None
|
||||
event.time_end = request.form.get('time_end') or None
|
||||
event.location = request.form.get('location', '').strip() or None
|
||||
event.event_type = event_type
|
||||
event.max_attendees = None if is_external else (request.form.get('max_attendees', type=int) or None)
|
||||
event.access_level = access_level
|
||||
event.is_external = is_external
|
||||
event.external_url = request.form.get('external_url', '').strip() or None if is_external else None
|
||||
event.external_source = request.form.get('external_source', '').strip() or None if is_external else None
|
||||
event.is_paid = is_paid
|
||||
event.price_member = request.form.get('price_member', type=float) if is_paid else None
|
||||
event.price_guest = request.form.get('price_guest', type=float) if is_paid else None
|
||||
|
||||
# Handle file attachment
|
||||
attachment = request.files.get('attachment')
|
||||
if attachment and attachment.filename:
|
||||
from werkzeug.utils import secure_filename
|
||||
import uuid
|
||||
filename = secure_filename(attachment.filename)
|
||||
stored_name = f"{uuid.uuid4().hex}_{filename}"
|
||||
upload_dir = os.path.join('static', 'uploads', 'events')
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
attachment.save(os.path.join(upload_dir, stored_name))
|
||||
event.attachment_filename = filename
|
||||
event.attachment_path = f"static/uploads/events/{stored_name}"
|
||||
|
||||
db.commit()
|
||||
flash('Wydarzenie zostało zaktualizowane.', 'success')
|
||||
return redirect(url_for('.admin_calendar'))
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
flash(f'Błąd: {str(e)}', 'error')
|
||||
|
||||
return render_template('calendar/admin_new.html', event=event)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/kalendarz/<int:event_id>/delete', methods=['POST'])
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
|
||||
@ -153,6 +153,12 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<a href="{{ url_for('admin.admin_calendar_edit', event_id=event.id) }}" class="btn-icon" title="Edytuj" style="color: var(--primary);">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||
</svg>
|
||||
</a>
|
||||
{% if event.is_paid %}
|
||||
<a href="{{ url_for('admin.admin_event_payments', event_id=event.id) }}" class="btn-icon" title="Platnosci" style="color: var(--success);">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
|
||||
@ -98,17 +98,17 @@
|
||||
</a>
|
||||
|
||||
<div class="form-header">
|
||||
<h1>Nowe wydarzenie</h1>
|
||||
<p class="text-muted">Dodaj spotkanie lub wydarzenie Norda Biznes</p>
|
||||
<h1>{{ 'Edytuj wydarzenie' if event else 'Nowe wydarzenie' }}</h1>
|
||||
<p class="text-muted">{{ 'Zmień dane wydarzenia' if event else 'Dodaj spotkanie lub wydarzenie Norda Biznes' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-card">
|
||||
<form method="POST" action="{{ url_for('admin.admin_calendar_new') }}" enctype="multipart/form-data">
|
||||
<form method="POST" action="{{ url_for('admin.admin_calendar_edit', event_id=event.id) if event else url_for('admin.admin_calendar_new') }}" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="form-group" style="background: var(--bg-secondary); padding: var(--spacing-md); border-radius: var(--radius); border: 1px solid var(--border);">
|
||||
<label style="display: flex; align-items: center; gap: var(--spacing-sm); cursor: pointer; margin-bottom: 0;">
|
||||
<input type="checkbox" id="is_external" name="is_external" style="width: auto;">
|
||||
<input type="checkbox" id="is_external" name="is_external" style="width: auto;"{{ ' checked' if event and event.is_external }}>
|
||||
<span>Wydarzenie zewnętrzne</span>
|
||||
</label>
|
||||
<div class="form-hint">Zaznacz dla wydarzeń organizowanych przez podmioty zewnętrzne (ARP, KIG, urzędy). Użytkownicy zobaczą przycisk "Jestem zainteresowany" zamiast "Zapisz się".</div>
|
||||
@ -117,50 +117,50 @@
|
||||
<div id="external-fields" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="external_source">Organizator / Źródło *</label>
|
||||
<input type="text" id="external_source" name="external_source" maxlength="255" placeholder="np. Agencja Rozwoju Pomorza">
|
||||
<input type="text" id="external_source" name="external_source" maxlength="255" placeholder="np. Agencja Rozwoju Pomorza" value="{{ event.external_source or '' if event else '' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="external_url">Link do rejestracji *</label>
|
||||
<input type="url" id="external_url" name="external_url" placeholder="https://brokereksportowy.pl/...">
|
||||
<input type="url" id="external_url" name="external_url" placeholder="https://brokereksportowy.pl/..." value="{{ event.external_url or '' if event else '' }}">
|
||||
<div class="form-hint">Link do strony zewnętrznej, gdzie użytkownicy mogą się zarejestrować</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="title">Tytul wydarzenia *</label>
|
||||
<input type="text" id="title" name="title" required maxlength="255" placeholder="np. Spotkanie czlonkow Norda Biznes">
|
||||
<input type="text" id="title" name="title" required maxlength="255" placeholder="np. Spotkanie czlonkow Norda Biznes" value="{{ event.title if event else '' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Opis</label>
|
||||
<textarea id="description" name="description" rows="4" placeholder="Opisz co bedzie sie dzialo na wydarzeniu..."></textarea>
|
||||
<textarea id="description" name="description" rows="4" placeholder="Opisz co bedzie sie dzialo na wydarzeniu...">{{ event.description or '' if event else '' }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="event_type">Typ wydarzenia</label>
|
||||
<select id="event_type" name="event_type">
|
||||
<option value="meeting">Spotkanie</option>
|
||||
<option value="webinar">Webinar</option>
|
||||
<option value="networking">Networking</option>
|
||||
<option value="rada">Rada Izby</option>
|
||||
<option value="other">Inne</option>
|
||||
<option value="meeting"{{ ' selected' if event and event.event_type == 'meeting' }}>Spotkanie</option>
|
||||
<option value="webinar"{{ ' selected' if event and event.event_type == 'webinar' }}>Webinar</option>
|
||||
<option value="networking"{{ ' selected' if event and event.event_type == 'networking' }}>Networking</option>
|
||||
<option value="rada"{{ ' selected' if event and event.event_type == 'rada' }}>Rada Izby</option>
|
||||
<option value="other"{{ ' selected' if event and event.event_type == 'other' }}>Inne</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="event_date">Data *</label>
|
||||
<input type="date" id="event_date" name="event_date" required>
|
||||
<input type="date" id="event_date" name="event_date" required value="{{ event.event_date.strftime('%Y-%m-%d') if event else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="access_level">Poziom dostępu *</label>
|
||||
<select id="access_level" name="access_level">
|
||||
<option value="members_only">Tylko członkowie Izby NORDA</option>
|
||||
<option value="rada_only">Tylko Rada Izby</option>
|
||||
<option value="public">Publiczne (wszyscy zalogowani)</option>
|
||||
<option value="members_only"{{ ' selected' if event and event.access_level == 'members_only' }}>Tylko członkowie Izby NORDA</option>
|
||||
<option value="rada_only"{{ ' selected' if event and event.access_level == 'rada_only' }}>Tylko Rada Izby</option>
|
||||
<option value="public"{{ ' selected' if event and event.access_level == 'public' }}>Publiczne (wszyscy zalogowani)</option>
|
||||
</select>
|
||||
<div class="form-hint">
|
||||
<strong>Członkowie</strong> - wszyscy członkowie Izby NORDA mogą widzieć i zapisać się<br>
|
||||
@ -172,41 +172,41 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="time_start">Godzina rozpoczecia</label>
|
||||
<input type="time" id="time_start" name="time_start">
|
||||
<input type="time" id="time_start" name="time_start" value="{{ event.time_start.strftime('%H:%M') if event and event.time_start else '' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="time_end">Godzina zakonczenia</label>
|
||||
<input type="time" id="time_end" name="time_end">
|
||||
<input type="time" id="time_end" name="time_end" value="{{ event.time_end.strftime('%H:%M') if event and event.time_end else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="location">Miejsce</label>
|
||||
<input type="text" id="location" name="location" maxlength="500" placeholder="np. ul. Tomasza Rogali 11, Wejherowo lub Online">
|
||||
<input type="text" id="location" name="location" maxlength="500" placeholder="np. ul. Tomasza Rogali 11, Wejherowo lub Online" value="{{ event.location or '' if event else '' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="location_url">Link do lokalizacji</label>
|
||||
<input type="url" id="location_url" name="location_url" placeholder="np. link do Google Maps lub Zoom">
|
||||
<input type="url" id="location_url" name="location_url" placeholder="np. link do Google Maps lub Zoom" value="{{ event.location_url or '' if event else '' }}">
|
||||
<div class="form-hint">Opcjonalny link do mapy lub platformy online</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="speaker_name">Prelegent</label>
|
||||
<input type="text" id="speaker_name" name="speaker_name" maxlength="255" placeholder="Imie i nazwisko">
|
||||
<input type="text" id="speaker_name" name="speaker_name" maxlength="255" placeholder="Imie i nazwisko" value="{{ event.speaker_name or '' if event else '' }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="max_attendees">Limit uczestnikow</label>
|
||||
<input type="number" id="max_attendees" name="max_attendees" min="1" placeholder="Pozostaw puste = bez limitu">
|
||||
<input type="number" id="max_attendees" name="max_attendees" min="1" placeholder="Pozostaw puste = bez limitu" value="{{ event.max_attendees or '' if event else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="background: var(--bg-secondary); padding: var(--spacing-md); border-radius: var(--radius); border: 1px solid var(--border);">
|
||||
<label style="display: flex; align-items: center; gap: var(--spacing-sm); cursor: pointer; margin-bottom: 0;">
|
||||
<input type="checkbox" id="is_paid" name="is_paid" style="width: auto;">
|
||||
<input type="checkbox" id="is_paid" name="is_paid" style="width: auto;"{{ ' checked' if event and event.is_paid }}>
|
||||
<span>Wydarzenie płatne</span>
|
||||
</label>
|
||||
<div class="form-hint">Zaznacz, jeśli uczestnicy muszą wnieść opłatę za udział.</div>
|
||||
@ -216,23 +216,26 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="price_member">Cena dla członków Izby (zł) *</label>
|
||||
<input type="number" id="price_member" name="price_member" min="0" step="1" placeholder="np. 140">
|
||||
<input type="number" id="price_member" name="price_member" min="0" step="1" placeholder="np. 140" value="{{ event.price_member|int if event and event.price_member else '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="price_guest">Cena dla gości (zł) *</label>
|
||||
<input type="number" id="price_guest" name="price_guest" min="0" step="1" placeholder="np. 240">
|
||||
<input type="number" id="price_guest" name="price_guest" min="0" step="1" placeholder="np. 240" value="{{ event.price_guest|int if event and event.price_guest else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="attachment">Załącznik (PDF, DOCX)</label>
|
||||
{% if event and event.attachment_filename %}
|
||||
<div class="form-hint" style="margin-bottom: 6px;">Aktualny: <strong>{{ event.attachment_filename }}</strong> — wybierz nowy plik, aby zastąpić</div>
|
||||
{% endif %}
|
||||
<input type="file" id="attachment" name="attachment" accept=".pdf,.docx,.doc">
|
||||
<div class="form-hint">Opcjonalny plik do pobrania przez uczestników (np. zaproszenie, agenda)</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Utworz wydarzenie</button>
|
||||
<button type="submit" class="btn btn-primary">{{ 'Zapisz zmiany' if event else 'Utworz wydarzenie' }}</button>
|
||||
<a href="{{ url_for('admin.admin_calendar') }}" class="btn btn-secondary">Anuluj</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -689,14 +689,23 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if event.is_paid and current_user.is_authenticated and current_user.can_access_admin_panel() %}
|
||||
<div style="margin-top: var(--spacing-md);">
|
||||
{% if current_user.is_authenticated and current_user.can_access_admin_panel() %}
|
||||
<div style="margin-top: var(--spacing-md); display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('admin.admin_calendar_edit', event_id=event.id) }}" class="btn btn-outline" style="font-size: 0.9em;">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="vertical-align: -3px; margin-right: 4px;">
|
||||
<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||
</svg>
|
||||
Edytuj wydarzenie
|
||||
</a>
|
||||
{% if event.is_paid %}
|
||||
<a href="{{ url_for('admin.admin_event_payments', event_id=event.id) }}" class="btn btn-outline" style="font-size: 0.9em;">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="vertical-align: -3px; margin-right: 4px;">
|
||||
<path d="M12 1v22M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>
|
||||
</svg>
|
||||
Zarządzaj płatnościami
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user