From 970f294c9bdf7e771f346654ad3c7772f57e268c Mon Sep 17 00:00:00 2001 From: kobold-ai Date: Tue, 22 Sep 2026 11:27:46 +0200 Subject: [PATCH] fix: hamburger menu vertical stack, lock paid orders, order cancellation with balance refund, deduplicate admin drawer --- app/main.py | 93 +++++++++++++++++++++++++++++++++++++++- app/static/style.css | 19 +++++++- app/templates/admin.html | 28 ++++++++---- app/templates/base.html | 44 ++++++++++--------- app/templates/buyer.html | 52 ++++++++++++---------- app/templates/index.html | 77 ++++++++++++++++++++++++++++----- 6 files changed, 247 insertions(+), 66 deletions(-) diff --git a/app/main.py b/app/main.py index 63261cf..29250ba 100644 --- a/app/main.py +++ b/app/main.py @@ -1,6 +1,7 @@ import os import re import secrets +import urllib.parse from datetime import datetime, timedelta, date from typing import Optional, Dict, List, Set from decimal import Decimal @@ -130,7 +131,14 @@ def seed_demo_users_if_needed(tenant: Tenant, db: Session): @app.get("/", response_class=HTMLResponse) -def index(request: Request, event_id: Optional[int] = None, token: Optional[str] = None, db: Session = Depends(get_db)): +def index( + request: Request, + event_id: Optional[int] = None, + token: Optional[str] = None, + success: Optional[str] = None, + error: Optional[str] = None, + db: Session = Depends(get_db) +): tenant = resolve_tenant(request, db) if not tenant: return templates.TemplateResponse(request, "not_found_tenant.html", context={}) @@ -207,6 +215,8 @@ def index(request: Request, event_id: Optional[int] = None, token: Optional[str] "existing_order": existing_order, "user_items": user_items, "order_count": order_count, + "success": success, + "error": error, "now": now }) @@ -456,6 +466,11 @@ async def place_order( ).first() if existing_order: + if existing_order.is_paid: + raise HTTPException( + status_code=400, + detail="Diese Bestellung wurde bereits als bezahlt verbucht und kann nicht mehr geändert werden. Bitte wende dich an deinen Organisator." + ) db.query(OrderItem).filter(OrderItem.order_id == existing_order.id).delete() existing_order.total_price = total_price existing_order.guest_display_name = guest_name_str @@ -484,6 +499,43 @@ async def place_order( return RedirectResponse(url=f"/order/{order_id}/success", status_code=status.HTTP_303_SEE_OTHER) +@app.post("/order/cancel") +def cancel_order( + request: Request, + event_id: int = Form(...), + db: Session = Depends(get_db) +): + tenant = resolve_tenant(request, db) + current_user = get_current_user(request, db) + if not current_user: + return RedirectResponse(url="/login", status_code=status.HTTP_303_SEE_OTHER) + + event = db.query(Event).filter(Event.id == event_id, Event.tenant_id == tenant.id).first() + if not event: + raise HTTPException(status_code=404, detail="Event nicht gefunden") + + if event.status != "open" or event.deadline < datetime.now(): + raise HTTPException(status_code=400, detail="Die Frist ist abgelaufen. Bestellungen können nicht mehr storniert werden.") + + order = db.query(Order).filter( + Order.tenant_id == tenant.id, + Order.event_id == event.id, + Order.user_id == current_user.id + ).first() + + if not order: + return RedirectResponse(url=f"/?event_id={event.id}", status_code=status.HTTP_303_SEE_OTHER) + + if order.is_paid: + raise HTTPException(status_code=400, detail="Bereits bezahlte Bestellungen können nur vom Organisator storniert werden.") + + db.delete(order) + db.commit() + + success_msg = urllib.parse.quote("Deine Bestellung wurde erfolgreich storniert") + return RedirectResponse(url=f"/?event_id={event.id}&success={success_msg}", status_code=status.HTTP_303_SEE_OTHER) + + @app.get("/order/{order_id}/success", response_class=HTMLResponse) def order_success(order_id: int, request: Request, db: Session = Depends(get_db)): tenant = resolve_tenant(request, db) @@ -1522,6 +1574,45 @@ def toggle_paid(order_id: int, request: Request, db: Session = Depends(get_db)): return RedirectResponse(url=f"/admin?event_id={ev_id}&tab=orders", status_code=status.HTTP_303_SEE_OTHER) +@app.post("/admin/orders/{order_id}/delete") +def admin_delete_order( + order_id: int, + request: Request, + db: Session = Depends(get_db) +): + tenant = resolve_tenant(request, db) + actor = require_organizer_or_admin(request, db) + + order = db.query(Order).filter(Order.id == order_id, Order.tenant_id == tenant.id).first() + if not order: + raise HTTPException(status_code=404, detail="Bestellung nicht gefunden") + + ev_id = order.event_id + + # Falls per Guthaben bezahlt, Betrag automatisch zurückerstatten! + if order.is_paid and order.payment_method == "balance" and order.user: + order.user.balance += order.total_price + ev_title = order.event.title if order.event else "Brotzeit" + tx = BalanceTransaction( + tenant_id=tenant.id, + user_id=order.user.id, + order_id=order.id, + amount=order.total_price, + transaction_type="refund", + description=f"Rückerstattung wegen Stornierung: {ev_title}", + created_by_id=actor.id + ) + db.add(tx) + + db.delete(order) + db.commit() + + referer = request.headers.get("referer", "") + if "/einkauf" in referer: + return RedirectResponse(url=f"/einkauf?event_id={ev_id}&success=Bestellung erfolgreich storniert und ggf. erstattet", status_code=status.HTTP_303_SEE_OTHER) + return RedirectResponse(url=f"/admin?event_id={ev_id}&tab=orders&success=Bestellung erfolgreich storniert und ggf. erstattet", status_code=status.HTTP_303_SEE_OTHER) + + @app.get("/health") def health(): return {"status": "ok", "app": "Schmankerl"} diff --git a/app/static/style.css b/app/static/style.css index db008f6..a1fe693 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -133,12 +133,29 @@ body { transform: translateY(-1px); } +.hamburger-icon { + display: flex; + flex-direction: column; + justify-content: center; + gap: 3.5px; + width: 16px; + height: 14px; + flex-shrink: 0; +} + +.hamburger-icon .bar, .menu-toggle-btn .bar { display: block; - width: 16px; + width: 100%; height: 2px; background-color: var(--text-main); border-radius: 2px; + transition: all 0.2s ease; +} + +.menu-toggle-btn:hover .hamburger-icon .bar, +.menu-toggle-btn:hover .bar { + background-color: var(--accent); } .menu-btn-text { diff --git a/app/templates/admin.html b/app/templates/admin.html index 6062547..4498b5f 100644 --- a/app/templates/admin.html +++ b/app/templates/admin.html @@ -295,15 +295,25 @@ {% if event.sponsor_name %}
von Sponsor{% endif %} - {% if event.sponsor_name %} - - {% else %} -
- -
- {% endif %} +
+ {% if event.sponsor_name %} + + {% else %} +
+ +
+ {% endif %} + +
+ +
+
{% else %} diff --git a/app/templates/base.html b/app/templates/base.html index 178a980..e596520 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -23,9 +23,11 @@