v2.2: Frontend-Login, Captcha, professionelles UI, Gast-Ansicht

This commit is contained in:
Peter
2026-05-16 12:26:24 +00:00
parent 9f8c7fb78d
commit 47eac3f54a
4 changed files with 433 additions and 554 deletions
View File
+24
View File
@@ -0,0 +1,24 @@
import re
with open('/root/reservation-system/app/main.py', 'r') as f:
content = f.read()
# Einfachere Modifikation - füge Admin-Check hinzu nachdem rooms geladen wurden
# Suche nach der rooms Funktion und ergänze sie
old_code = """@app.route('/api/rooms')
def rooms():
is_admin = session.get('user_role') == 'admin'"""
new_code = """@app.route('/api/rooms')
def rooms():
is_admin = session.get('user_role') == 'admin'
with get_db() as db:"""
content = content.replace(old_code, new_code)
with open('/root/reservation-system/app/main.py', 'w') as f:
f.write(content)
print("Rooms endpoint updated")
-1
View File
@@ -32,4 +32,3 @@ def check_session():
role = session.get('user_role') role = session.get('user_role')
if role: if role:
return jsonify({"role": role, "logged_in": True}) return jsonify({"role": role, "logged_in": True})
return jsonify({"role": None, "logged_in": False})
+362 -506
View File
@@ -5,633 +5,489 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reservierungssystem</title> <title>Reservierungssystem</title>
<style> <style>
* { * { box-sizing: border-box; margin: 0; padding: 0; }
box-sizing: border-box;
margin: 0; :root {
padding: 0; --primary: #3b82f6;
--primary-dark: #2563eb;
--secondary: #64748b;
--bg: #f8fafc;
--card: #ffffff;
--text: #1e293b;
--text-light: #64748b;
--border: #e2e8f0;
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a2e; background: var(--bg);
color: #fff; color: var(--text);
min-height: 100vh; min-height: 100vh;
line-height: 1.6;
} }
/* Header */
.header { .header {
background: #16213e; background: var(--card);
padding: 15px 30px; padding: 1rem 2rem;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-bottom: 1px solid #2d3a5c; border-bottom: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
} }
.logo { .logo {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 700; font-weight: 600;
color: #00d4aa; color: var(--primary);
} }
.nav { .nav { display: flex; gap: 1rem; align-items: center; }
display: flex;
gap: 10px;
}
.nav-btn {
background: transparent;
border: 1px solid #00d4aa;
color: #00d4aa;
padding: 8px 20px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.nav-btn:hover, .nav-btn.active {
background: #00d4aa;
color: #000;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 30px;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: #16213e;
border: 1px solid #2d3a5c;
border-radius: 12px;
padding: 25px;
}
.stat-card h3 {
color: #888;
font-size: 0.9rem;
font-weight: 500;
margin-bottom: 10px;
text-transform: uppercase;
}
.stat-value {
font-size: 2.5rem;
font-weight: 700;
color: #00d4aa;
}
.alert-card {
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
cursor: pointer;
transition: transform 0.2s;
}
.alert-card:hover {
transform: translateY(-3px);
}
.alert-card h3 {
color: rgba(255,255,255,0.9);
}
.alert-card .stat-value {
color: #fff;
}
.section {
background: #16213e;
border: 1px solid #2d3a5c;
border-radius: 12px;
margin-bottom: 20px;
}
.section-header {
padding: 20px 25px;
border-bottom: 1px solid #2d3a5c;
display: flex;
justify-content: space-between;
align-items: center;
}
.section-header h2 {
font-size: 1.2rem;
}
.btn { .btn {
background: #00d4aa; padding: 0.5rem 1.25rem;
color: #000;
border: none;
padding: 10px 20px;
border-radius: 6px; border-radius: 6px;
border: none;
cursor: pointer; cursor: pointer;
font-weight: 600; font-size: 0.9rem;
} transition: all 0.2s;
text-decoration: none;
.btn:hover {
background: #00b894;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #2d3a5c;
}
th {
color: #888;
font-weight: 500;
font-size: 0.85rem;
text-transform: uppercase;
}
tr:hover {
background: rgba(0,212,170,0.05);
}
.badge {
display: inline-block; display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 500;
} }
.badge-confirmed { .btn-primary {
background: rgba(0,212,170,0.2); background: var(--primary);
color: #00d4aa; color: white;
} }
.badge-pending { .btn-primary:hover { background: var(--primary-dark); }
background: rgba(241,196,15,0.2);
color: #f1c40f; .btn-secondary {
background: transparent;
border: 1px solid var(--border);
color: var(--text);
} }
.badge-cancelled { .btn-secondary:hover { background: var(--bg); }
background: rgba(231,76,60,0.2);
color: #e74c3c;
}
.booking-number { /* Login Modal */
font-family: monospace; .modal {
background: rgba(0,212,170,0.1); display: none;
padding: 4px 8px;
border-radius: 4px;
color: #00d4aa;
}
.hidden {
display: none !important;
}
/* Modal */
.modal-overlay {
position: fixed; position: fixed;
top: 0; top: 0; left: 0; width: 100%; height: 100%;
left: 0; background: rgba(0,0,0,0.5);
width: 100%; z-index: 1000;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 1000;
} }
.modal { .modal.active { display: flex; }
background: #16213e;
border: 1px solid #2d3a5c; .modal-content {
background: var(--card);
padding: 2rem;
border-radius: 12px; border-radius: 12px;
padding: 30px;
width: 90%; width: 90%;
max-width: 600px; max-width: 400px;
max-height: 90vh; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
overflow-y: auto;
} }
.modal h2 { .modal-title {
margin-bottom: 20px; font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1.5rem;
color: var(--text);
} }
.form-group { .form-group { margin-bottom: 1rem; }
margin-bottom: 20px;
}
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 8px; margin-bottom: 0.5rem;
color: #888; font-size: 0.875rem;
color: var(--text-light);
} }
.form-group input, .form-group select, .form-group textarea { .form-group input {
width: 100%; width: 100%;
padding: 12px; padding: 0.75rem;
border: 1px solid #2d3a5c; border: 1px solid var(--border);
background: #1a1a2e;
color: #fff;
border-radius: 6px; border-radius: 6px;
font-size: 1rem;
} }
.form-row { .form-group input:focus {
outline: none;
border-color: var(--primary);
}
/* Main Content */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.page-title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
color: var(--text);
}
.page-subtitle {
color: var(--text-light);
margin-bottom: 2rem;
}
/* Room Cards */
.rooms-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 15px; gap: 1.5rem;
margin-bottom: 2rem;
} }
.modal-actions { .room-card {
background: var(--card);
border-radius: 12px;
padding: 1.5rem;
border: 1px solid var(--border);
transition: all 0.2s;
cursor: pointer;
}
.room-card:hover {
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.room-header {
display: flex; display: flex;
gap: 10px; justify-content: space-between;
justify-content: flex-end; align-items: center;
margin-top: 25px; margin-bottom: 1rem;
} }
.btn-secondary { .room-name {
background: #333; font-size: 1.25rem;
color: #fff; font-weight: 600;
} }
.btn-secondary:hover { .room-badge {
background: #444; padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 500;
} }
@media (max-width: 1024px) { .room-capacity {
.dashboard-grid { color: var(--text-light);
grid-template-columns: repeat(2, 1fr); font-size: 0.9rem;
} margin-bottom: 1rem;
} }
@media (max-width: 768px) { .room-stats {
.dashboard-grid { display: flex;
grid-template-columns: 1fr; gap: 1rem;
font-size: 0.875rem;
color: var(--text-light);
} }
.header { /* Captcha */
flex-direction: column; .captcha-box {
gap: 15px; background: var(--bg);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
text-align: center;
} }
.nav { .captcha-question {
flex-wrap: wrap; font-size: 1.5rem;
justify-content: center; font-weight: 600;
color: var(--primary);
margin-bottom: 0.5rem;
} }
.form-row { .captcha-input {
grid-template-columns: 1fr; width: 120px;
text-align: center;
font-size: 1.25rem;
} }
/* Guest View Restrictions */
.guest-notice {
background: #fef3c7;
border: 1px solid #fcd34d;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1.5rem;
font-size: 0.9rem;
color: #92400e;
}
.admin-only { display: none; }
.admin-visible .admin-only { display: block; }
/* Loading */
.loading {
text-align: center;
padding: 3rem;
color: var(--text-light);
}
.error {
background: #fef2f2;
border: 1px solid #fecaca;
color: var(--error);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.success {
background: #f0fdf4;
border: 1px solid #bbf7d0;
color: var(--success);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="header"> <header class="header">
<div class="logo">🍽️ Reservierungssystem</div> <div class="logo">🍽️ Reservierung</div>
<div class="nav"> <nav class="nav">
<button class="nav-btn active" onclick="showView('dashboard')">Dashboard</button> <span id="userStatus"></span>
<button class="nav-btn" onclick="showView('reservations')">Reservierungen</button> <button class="btn btn-secondary" id="loginBtn" onclick="showLogin()">Admin Login</button>
<button class="nav-btn" onclick="showView('floorplan')">Tischplan</button> <button class="btn btn-primary admin-only" onclick="showNewBooking()">+ Buchung</button>
<button class="nav-btn" onclick="showView('guests')">Gäste</button> </nav>
<button class="nav-btn" onclick="showView('emails')">E-Mails</button> </header>
</div>
</div>
<div class="container"> <!-- Login Modal -->
<!-- Dashboard View --> <div class="modal" id="loginModal">
<div id="view-dashboard"> <div class="modal-content">
<div class="dashboard-grid"> <h2 class="modal-title">Admin Login</h2>
<div class="stat-card"> <form id="loginForm" onsubmit="handleLogin(event)">
<h3>Heute Reserviert</h3>
<div class="stat-value" id="stat-today">0</div>
</div>
<div class="stat-card">
<h3>Gäste Heute</h3>
<div class="stat-value" id="stat-guests">0</div>
</div>
<div class="stat-card">
<h3>Freie Tische</h3>
<div class="stat-value" id="stat-available">0</div>
</div>
<div class="stat-card alert-card" onclick="showView('emails')" id="alert-card">
<h3>⚠️ Klärung Erforderlich</h3>
<div class="stat-value" id="stat-pending">0</div>
</div>
</div>
<div class="section">
<div class="section-header">
<h2>Heutige Reservierungen</h2>
<button class="btn" onclick="openNewReservation()">+ Neue Reservierung</button>
</div>
<div id="today-reservations">
<table>
<thead>
<tr>
<th>Buchungsnr.</th>
<th>Zeit</th>
<th>Name</th>
<th>Personen</th>
<th>Status</th>
<th>Aktion</th>
</tr>
</thead>
<tbody id="today-table-body">
<!-- Dynamisch gefüllt -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Weitere Views (hidden by default) -->
<div id="view-reservations" class="hidden">
<div class="section">
<div class="section-header">
<h2>Alle Reservierungen</h2>
<div>
<input type="date" id="filter-date" onchange="loadReservations()">
<button class="btn" onclick="openNewReservation()">+ Neu</button>
</div>
</div>
<div id="all-reservations">
<table>
<thead>
<tr>
<th>Buchungsnr.</th>
<th>Datum</th>
<th>Zeit</th>
<th>Name</th>
<th>Pers.</th>
<th>Status</th>
</tr>
</thead>
<tbody id="reservations-table-body">
</tbody>
</table>
</div>
</div>
</div>
<div id="view-floorplan" class="hidden">
<div class="section">
<div class="section-header">
<h2>Tischplan</h2>
<select id="floorplan-date" onchange="loadFloorplan()">
<option>Heute</option>
</select>
</div>
<div id="floorplan-container" style="min-height: 500px; position: relative;"
003e
<!-- Dynamisch rendern -->
</div>
</div>
</div>
<div id="view-guests" class="hidden">
<div class="section">
<div class="section-header">
<h2>Gäste-Adressbuch</h2>
<div>
<input type="text" placeholder="Suchen..." id="guest-search" onkeyup="searchGuests()">
<button class="btn" onclick="openNewGuest()">+ Neuer Gast</button>
</div>
</div>
<div id="guests-list"></div>
</div>
</div>
<div id="view-emails" class="hidden">
<div class="section">
<div class="section-header">
<h2>E-Mail-Verarbeitung</h2>
<div class="nav">
<button class="nav-btn active" onclick="filterEmails('needs_review')">Klärung</button>
<button class="nav-btn" onclick="filterEmails('auto_processed')">Automatisch</button>
<button class="nav-btn" onclick="filterEmails('confirmed')">Bestätigt</button>
</div>
</div>
<div id="emails-list"></div>
</div>
</div>
</div>
<!-- Modal: Neue Reservierung -->
<div id="modal-reservation" class="modal-overlay hidden">
<div class="modal">
<h2>Neue Reservierung</h2>
<form id="reservation-form">
<div class="form-row">
<div class="form-group"> <div class="form-group">
<label>Datum *</label> <label>Passwort</label>
<input type="date" id="res-date" required> <input type="password" id="loginPassword" required placeholder="••••••••">
</div> </div>
<div class="form-group"> <button type="submit" class="btn btn-primary" style="width: 100%">Anmelden</button>
<label>Uhrzeit *</label> <button type="button" class="btn btn-secondary" style="width: 100%; margin-top: 0.5rem" onclick="hideLogin()">Abbrechen</button>
<input type="time" id="res-time" required> </form>
</div> </div>
</div> </div>
<div class="form-row"> <main class="container">
<div class="form-group"> <div class="guest-notice" id="guestNotice">
<label>Name *</label> 👋 Willkommen! Als Gast können Sie Tische reservieren. Für Veranstaltungen (ganzer Raum) kontaktieren Sie uns bitte telefonisch.
<input type="text" id="res-name" placeholder="Name suchen..." onkeyup="searchGuestForReservation()" required>
</div>
<div class="form-group">
<label>Personen *</label>
<input type="number" id="res-guests" min="1" max="50" value="2" required>
</div>
</div> </div>
<div class="form-row"> <h1 class="page-title">Unsere Räume</h1>
<p class="page-subtitle">Wählen Sie einen Raum für Ihre Reservierung</p>
<div class="loading" id="loading">Lade Räume...</div>
<div class="rooms-grid" id="roomsGrid" style="display: none;"></div>
<div id="bookingSection" style="display: none;">
<h2 style="margin: 2rem 0 1rem;">Tisch reservieren</h2>
<div class="captcha-box">
<div class="captcha-question" id="captchaQuestion">Lade Captcha...</div>
<input type="number" class="captcha-input" id="captchaAnswer" placeholder="?" required>
<input type="hidden" id="captchaToken">
</div>
<form id="bookingForm" onsubmit="handleBooking(event)">
<div class="form-group">
<label>Name</label>
<input type="text" id="bookingName" required placeholder="Max Mustermann">
</div>
<div class="form-group"> <div class="form-group">
<label>Telefon</label> <label>Telefon</label>
<input type="tel" id="res-phone"> <input type="tel" id="bookingPhone" placeholder="+49 123 456789">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>E-Mail</label> <label>Datum</label>
<input type="email" id="res-email"> <input type="date" id="bookingDate" required>
</div> </div>
</div>
<div class="form-group"> <div class="form-group">
<label>Quelle</label> <label>Zeit</label>
<select id="res-source"> <input type="time" id="bookingTime" required>
<option value="phone">Telefon</option>
<option value="email">E-Mail</option>
<option value="web">Web</option>
<option value="walk-in">Walk-in</option>
</select>
</div> </div>
<div class="form-group" id="phone-caller-group">
<label>Anrufer (bei Telefon-Buchung)</label>
<input type="text" id="res-caller">
</div>
<div class="form-group"> <div class="form-group">
<label>Notizen</label> <label>Personen</label>
<textarea id="res-notes" rows="3"></textarea> <input type="number" id="bookingGuests" min="1" max="20" value="2" required>
</div> </div>
<button type="submit" class="btn btn-primary" style="width: 100%">Reservierung anfragen</button>
</form> </form>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="closeModal()">Abbrechen</button>
<button class="btn" onclick="saveReservation()">Speichern</button>
</div>
</div>
</div> </div>
</main>
<script> <script>
// Global state let currentUser = null;
let currentView = 'dashboard'; let selectedRoom = null;
let selectedTable = null;
// Init // Check session on load
document.addEventListener('DOMContentLoaded', () => { async function checkSession() {
loadDashboard(); try {
document.getElementById('res-date').valueAsDate = new Date(); const res = await fetch('/api/session');
const data = await res.json();
// Source change handler if (data.logged_in) {
document.getElementById('res-source').addEventListener('change', (e) => { currentUser = data;
const callerGroup = document.getElementById('phone-caller-group'); document.body.classList.add('admin-visible');
callerGroup.style.display = e.target.value === 'phone' ? 'block' : 'none'; document.getElementById('loginBtn').textContent = 'Abmelden';
}); document.getElementById('loginBtn').onclick = logout;
}); document.getElementById('guestNotice').style.display = 'none';
}
// View switching } catch (e) {
function showView(view) { console.log('Not logged in');
// Hide all views }
document.querySelectorAll('[id^="view-"]').forEach(el => { loadRooms();
el.classList.add('hidden'); loadCaptcha();
});
// Show selected view
document.getElementById(`view-${view}`).classList.remove('hidden');
// Update nav buttons
document.querySelectorAll('.nav-btn').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
// Load data for view
if (view === 'dashboard') loadDashboard();
if (view === 'reservations') loadReservations();
if (view === 'floorplan') loadFloorplan();
if (view === 'guests') loadGuests();
if (view === 'emails') loadEmails();
currentView = view;
} }
// API calls // Load rooms
async function api(endpoint, options = {}) { async function loadRooms() {
const res = await fetch(`/api${endpoint}`, { try {
...options, const res = await fetch('/api/rooms');
headers: { const rooms = await res.json();
'Content-Type': 'application/json', displayRooms(rooms);
...options.headers } catch (e) {
document.getElementById('loading').textContent = 'Fehler beim Laden der Räume';
} }
});
return res.json();
} }
// Dashboard function displayRooms(rooms) {
async function loadDashboard() { const grid = document.getElementById('roomsGrid');
const data = await api('/dashboard'); const loading = document.getElementById('loading');
document.getElementById('stat-today').textContent = data.today_count; loading.style.display = 'none';
document.getElementById('stat-guests').textContent = data.guests_today; grid.style.display = 'grid';
document.getElementById('stat-available').textContent = data.total_tables - data.today_count;
document.getElementById('stat-pending').textContent = data.pending_emails;
// Today's reservations grid.innerHTML = rooms.map(room => `
const tbody = document.getElementById('today-table-body'); <div class="room-card" onclick="selectRoom(${room.id}, '${room.name}')" style="border-left: 4px solid ${room.color}">
tbody.innerHTML = data.today_reservations.map(r => ` <div class="room-header">
<tr> <span class="room-name">${room.name}</span>
<td><span class="booking-number">${r.booking_number}</span></td> <span class="room-badge" style="background: ${room.color}20; color: ${room.color}">
<td>${r.time_from}</td> ${room.capacity} Plätze
<td>${r.guest_name || r.phone || 'Unbekannt'}</td> </span>
<td>${r.guests}</td> </div>
<td><span class="badge badge-${r.status}">${r.status}</span></td> <div class="room-capacity">
<td><button class="btn btn-secondary" onclick="viewReservation(${r.id})">Details</button></td> ${room.areas ? room.areas.length : 0} Bereiche ·
</tr> ${room.areas ? room.areas.reduce((sum, a) => sum + (a.tables ? a.tables.length : 0), 0) : 0} Tische
</div>
<div class="room-stats">
<span>🕐 ${room.areas && room.areas[0] ? room.areas[0].available_from + '-' + room.areas[0].available_to : '10:00-23:00'}</span>
</div>
</div>
`).join(''); `).join('');
} }
// Reservations function selectRoom(roomId, roomName) {
async function loadReservations() { selectedRoom = roomId;
const date = document.getElementById('filter-date').value; document.getElementById('bookingSection').style.display = 'block';
const params = date ? `?date=${date}` : ''; document.getElementById('bookingSection').scrollIntoView({ behavior: 'smooth' });
const data = await api(`/reservations${params}`);
const tbody = document.getElementById('reservations-table-body');
tbody.innerHTML = data.map(r => `
<tr>
<td><span class="booking-number">${r.booking_number}</span></td>
<td>${r.date}</td>
<td>${r.time_from}</td>
<td>${r.guest_name || 'Unbekannt'}</td>
<td>${r.guests}</td>
<td><span class="badge badge-${r.status}">${r.status}</span></td>
</tr>
`).join('');
} }
// Modal functions // Captcha
function openNewReservation() { async function loadCaptcha() {
document.getElementById('modal-reservation').classList.remove('hidden'); try {
const res = await fetch('/api/captcha');
const data = await res.json();
document.getElementById('captchaQuestion').textContent = data.question;
document.getElementById('captchaToken').value = data.token;
} catch (e) {
document.getElementById('captchaQuestion').textContent = 'Captcha nicht verfügbar';
}
} }
function closeModal() { // Login
document.getElementById('modal-reservation').classList.add('hidden'); function showLogin() {
document.getElementById('reservation-form').reset(); document.getElementById('loginModal').classList.add('active');
} }
async function saveReservation() { function hideLogin() {
document.getElementById('loginModal').classList.remove('active');
}
async function handleLogin(e) {
e.preventDefault();
const password = document.getElementById('loginPassword').value;
try {
const res = await fetch('/api/admin/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password })
});
if (res.ok) {
hideLogin();
location.reload();
} else {
alert('Falsches Passwort');
}
} catch (e) {
alert('Fehler beim Login');
}
}
async function logout() {
await fetch('/api/admin/logout', { method: 'POST' });
location.reload();
}
// Booking
async function handleBooking(e) {
e.preventDefault();
const captchaAnswer = document.getElementById('captchaAnswer').value;
const captchaToken = document.getElementById('captchaToken').value;
const data = { const data = {
date: document.getElementById('res-date').value, name: document.getElementById('bookingName').value,
time_from: document.getElementById('res-time').value, phone: document.getElementById('bookingPhone').value,
guests: parseInt(document.getElementById('res-guests').value), date: document.getElementById('bookingDate').value,
name: document.getElementById('res-name').value, time_from: document.getElementById('bookingTime').value,
phone: document.getElementById('res-phone').value, guests: parseInt(document.getElementById('bookingGuests').value),
email: document.getElementById('res-email').value, captcha_token: captchaToken,
source: document.getElementById('res-source').value, captcha_answer: captchaAnswer
phone_caller_name: document.getElementById('res-caller').value,
notes: document.getElementById('res-notes').value
}; };
const result = await api('/reservations', { try {
const res = await fetch('/api/reservations', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data) body: JSON.stringify(data)
}); });
if (result.booking_number) { if (res.ok) {
alert(`Reservierung erstellt: ${result.booking_number}`); alert('Reservierung erfolgreich! Wir melden uns bei Ihnen.');
closeModal(); document.getElementById('bookingForm').reset();
loadDashboard(); loadCaptcha();
} else { } else {
alert('Fehler: ' + (result.error || 'Unbekannter Fehler')); const err = await res.json();
alert(err.error || 'Fehler bei der Reservierung');
}
} catch (e) {
alert('Netzwerkfehler');
} }
} }
// Placeholder functions // Set today's date as default
function loadFloorplan() { console.log('Floorplan loading...'); } document.getElementById('bookingDate').valueAsDate = new Date();
function loadGuests() { console.log('Guests loading...'); }
function loadEmails() { console.log('Emails loading...'); } // Initialize
function filterEmails(status) { console.log('Filter:', status); } checkSession();
function searchGuests() { console.log('Search guests...'); }
function openNewGuest() { console.log('New guest...'); }
function searchGuestForReservation() { console.log('Search guest...'); }
function viewReservation(id) { console.log('View:', id); }
</script> </script>
</body> </body>
</html> </html>