diff --git a/app.py b/app.py index dc912d8..0beea4c 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,3 @@ - from flask import Flask, request, jsonify, session, render_template_string, redirect import os import json @@ -24,7 +23,7 @@ default_users = [ 'is_active': True, 'created_at': datetime.now().isoformat(), 'last_login': None, - 'force_password_change': False # Nach erstmaligem Login Passwort ändern + 'force_password_change': False } ] @@ -79,7 +78,7 @@ Buchungsdetails: - Datum: {date} - Uhrzeit: {time} - Personen: {guests} -- Tische: {tables} +- Raum: {room} Wir freuen uns auf Ihren Besuch! @@ -124,12 +123,14 @@ Ihr Restaurant-Team """ } -# Datenstruktur +# Datenstruktur - Räume mit Kapazität und Block-Status rooms = [ { 'id': 1, 'name': 'Hauptraum', 'description': 'Großer Saal mit Fensterfront', + 'capacity': 40, + 'blocked': False, 'tables': [ {'id': 1, 'name': 'Tisch 1', 'seats': 4, 'shape': 'circle'}, {'id': 2, 'name': 'Tisch 2', 'seats': 2, 'shape': 'circle'}, @@ -141,6 +142,8 @@ rooms = [ 'id': 2, 'name': 'Nebenraum', 'description': 'Gemütlicher kleiner Raum', + 'capacity': 20, + 'blocked': False, 'tables': [ {'id': 5, 'name': 'Tisch A', 'seats': 4, 'shape': 'circle'}, {'id': 6, 'name': 'Tisch B', 'seats': 4, 'shape': 'circle'}, @@ -150,6 +153,8 @@ rooms = [ 'id': 3, 'name': 'Terrasse', 'description': 'Draußen unter dem Vordach', + 'capacity': 15, + 'blocked': False, 'tables': [ {'id': 7, 'name': 'Tisch T1', 'seats': 4, 'shape': 'rect'}, {'id': 8, 'name': 'Tisch T2', 'seats': 4, 'shape': 'rect'}, @@ -166,6 +171,12 @@ def generate_booking_id(): if not any(r.get('booking_id') == booking_id for r in reservations): return booking_id +def get_room(room_id): + for room in rooms: + if room['id'] == room_id: + return room + return None + def get_table(table_id): for room in rooms: for table in room['tables']: @@ -176,11 +187,14 @@ def get_table(table_id): def get_available_tables(date_str, time_str): available = [] for room in rooms: + if room.get('blocked', False): + continue for table in room['tables']: table_copy = table.copy() table_copy['room_id'] = room['id'] table_copy['room_name'] = room['name'] - available.append(table_copy) + if is_table_available(table['id'], date_str, time_str): + available.append(table_copy) return available def is_table_available(table_id, date_str, time_str): @@ -190,6 +204,44 @@ def is_table_available(table_id, date_str, time_str): return False return True +def auto_assign_tables(room_id, guests, date_str, time_str): + """Automatische Tisch-Zuweisung basierend auf Raum und Gästeanzahl""" + room = get_room(room_id) + if not room: + return None, "Raum nicht gefunden" + + if room.get('blocked', False): + return None, "Raum ist derzeit nicht verfügbar" + + # Alle verfügbaren Tische im Raum + available_tables = [] + for table in room['tables']: + if is_table_available(table['id'], date_str, time_str): + available_tables.append(table) + + if not available_tables: + return None, "Keine Tische im Raum verfügbar" + + # Sortiere nach Sitzplätzen (größte zuerst) + available_tables.sort(key=lambda t: t['seats'], reverse=True) + + # Finde beste Kombination für Gäste + total_seats = sum(t['seats'] for t in available_tables) + if total_seats < guests: + return None, f"Nicht genügend Platz im Raum. Verfügbar: {total_seats} Plätze" + + # Wähle Tische aus (greedy - erst große, dann kleine) + selected_tables = [] + remaining_guests = guests + + for table in available_tables: + if remaining_guests <= 0: + break + selected_tables.append(table) + remaining_guests -= table['seats'] + + return [t['id'] for t in selected_tables], None + def send_email_smtp(to_email, subject, body): try: import smtplib @@ -251,21 +303,9 @@ PUBLIC_RESERVATION_HTML = ''' width: 100%; padding: 1rem; border: 2px solid #e0e0e0; border-radius: 10px; font-size: 1rem; } - .form-group input:focus { outline: none; border-color: #667eea; } - .checkbox-group { - display: flex; align-items: center; gap: 0.75rem; - padding: 1rem; background: #f8f9fa; border-radius: 10px; - border: 2px solid #e0e0e0; cursor: pointer; - } - .checkbox-group input[type="checkbox"] { width: 24px; height: 24px; } - .table-selection { - display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.75rem; - } - .table-option { - border: 2px solid #e0e0e0; border-radius: 12px; padding: 1rem; - text-align: center; cursor: pointer; transition: all 0.2s; - } - .table-option.selected { border-color: #667eea; background: #f0f4ff; } + .form-group input:focus, .form-group select:focus { outline: none; border-color: #667eea; } + .error-msg { display: none; color: #ef4444; font-size: 0.85rem; margin-top: 0.5rem; } + .success-message { text-align: center; padding: 3rem 2rem; } .btn { width: 100%; padding: 1.25rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); @@ -273,12 +313,13 @@ PUBLIC_RESERVATION_HTML = ''' font-size: 1.1rem; font-weight: 600; cursor: pointer; } .btn:disabled { opacity: 0.6; cursor: not-allowed; } - .success-message { text-align: center; padding: 3rem 2rem; } .booking-id { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 1rem 2rem; border-radius: 10px; font-size: 1.5rem; font-weight: bold; margin: 1rem 0; letter-spacing: 2px; } + .hidden { display: none !important; } + .capacity-info { color: #667eea; font-size: 0.9rem; margin-top: 0.5rem; } @@ -314,22 +355,20 @@ PUBLIC_RESERVATION_HTML = '''

👥 Wie viele Personen?

- +
-

🪑 Welcher Raum?

+

🏠 Welcher Raum?

- - - - - -
+
Bitte wählen Sie einen Raum aus
+
+
@@ -353,14 +392,6 @@ PUBLIC_RESERVATION_HTML = ''' -
-

🤖 Sicherheitsprüfung

- -
-
@@ -470,7 +522,7 @@ PUBLIC_RESERVATION_HTML = ''' ''' -# Admin Login mit Passwort-Änderung-Aufforderung +# Admin HTML Templates... ADMIN_LOGIN_HTML = ''' @@ -549,7 +601,6 @@ ADMIN_LOGIN_HTML = ''' ''' -# Passwort-Änderung Seite CHANGE_PASSWORD_HTML = ''' @@ -709,6 +760,8 @@ ADMIN_DASHBOARD_HTML = ''' padding: 0.75rem 1.5rem; border-radius: 8px; cursor: pointer; } .btn-secondary { background: #6b7280; } .btn-danger { background: #ef4444; } + .btn-success { background: #10b981; } + .btn-warning { background: #f59e0b; } .btn-sm { padding: 0.5rem 1rem; font-size: 0.85rem; } .search-box { display: flex; gap: 0.5rem; margin-bottom: 1rem; } .search-box input { flex: 1; padding: 0.75rem; border: 1px solid #ddd; border-radius: 8px; } @@ -719,6 +772,8 @@ ADMIN_DASHBOARD_HTML = ''' .badge-admin { background: #ef4444; color: white; } .badge-manager { background: #f59e0b; color: white; } .badge-staff { background: #10b981; color: white; } + .badge-blocked { background: #ef4444; color: white; } + .badge-active { background: #10b981; color: white; } .form-group { margin-bottom: 1rem; } .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 500; color: #555; } .form-group input, .form-group select { width: 100%; padding: 0.75rem; border: 1px solid #ddd; border-radius: 8px; } @@ -726,12 +781,14 @@ ADMIN_DASHBOARD_HTML = ''' .modal-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1000; justify-content: center; align-items: center; } .modal-overlay.active { display: flex; } - .modal { background: white; border-radius: 16px; padding: 2rem; width: 90%; max-width: 500px; max-height: 90vh; overflow-y: auto; } + .modal { background: white; border-radius: 16px; padding: 2rem; width: 90%; max-width: 600px; max-height: 90vh; overflow-y: auto; } .modal h2 { margin-bottom: 1.5rem; } + .modal h3 { margin: 1.5rem 0 0.75rem 0; color: #333; } .hidden { display: none !important; } .alert { padding: 1rem; border-radius: 8px; margin-bottom: 1rem; } .alert.success { background: #d1fae5; color: #065f46; } .alert.error { background: #fee2e2; color: #991b1b; } + .alert.info { background: #dbeafe; color: #1e40af; } .permission-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; } .permission-tag { background: #e0e7ff; color: #4338ca; padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; } .stats-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; } @@ -745,6 +802,15 @@ ADMIN_DASHBOARD_HTML = ''' .dropdown-content a:hover { background: #f8f9fa; } .dropdown-content a:last-child { border-bottom: none; } .dropdown:hover .dropdown-content { display: block; } + .room-card { border: 2px solid #e0e0e0; border-radius: 12px; padding: 1.5rem; transition: all 0.2s; } + .room-card:hover { border-color: #667eea; } + .room-card.blocked { background: #fef2f2; border-color: #ef4444; } + .room-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; } + .room-title { font-size: 1.25rem; font-weight: 600; } + .room-capacity { color: #666; font-size: 0.9rem; } + .tables-list { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; } + .table-item { background: #f3f4f6; padding: 0.5rem 0.75rem; border-radius: 6px; font-size: 0.85rem; } + .action-btns { display: flex; gap: 0.5rem; margin-top: 1rem; } @media (max-width: 768px) { .stats-grid { grid-template-columns: repeat(2, 1fr); } } @@ -789,8 +855,8 @@ ADMIN_DASHBOARD_HTML = '''
Diese Woche
-
0
-
Benutzer
+
0
+
Räume
@@ -821,7 +887,13 @@ ADMIN_DASHBOARD_HTML = ''' @@ -841,7 +913,7 @@ ADMIN_DASHBOARD_HTML = '''

✉️ E-Mail Templates

Buchungsbestätigung

-

Variablen: {name}, {booking_id}, {date}, {time}, {guests}, {tables}

+

Variablen: {name}, {booking_id}, {date}, {time}, {guests}, {room}

E-Mail-Antwort

@@ -879,6 +951,57 @@ ADMIN_DASHBOARD_HTML = ''' + + + + + +