Files
reservierungssystem/app/templates/index.html
T

497 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reservierungssystem</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--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 {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
line-height: 1.6;
}
/* Header */
.header {
background: var(--card);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.logo {
font-size: 1.5rem;
font-weight: 600;
color: var(--primary);
}
.nav { display: flex; gap: 1rem; align-items: center; }
.btn {
padding: 0.5rem 1.25rem;
border-radius: 6px;
border: none;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.2s;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover { background: var(--primary-dark); }
.btn-secondary {
background: transparent;
border: 1px solid var(--border);
color: var(--text);
}
.btn-secondary:hover { background: var(--bg); }
/* Login Modal */
.modal {
display: none;
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.active { display: flex; }
.modal-content {
background: var(--card);
padding: 2rem;
border-radius: 12px;
width: 90%;
max-width: 400px;
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1);
}
.modal-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1.5rem;
color: var(--text);
}
.form-group { margin-bottom: 1rem; }
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
color: var(--text-light);
}
.form-group input {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: 6px;
font-size: 1rem;
}
.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;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.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;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.room-name {
font-size: 1.25rem;
font-weight: 600;
}
.room-badge {
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 500;
}
.room-capacity {
color: var(--text-light);
font-size: 0.9rem;
margin-bottom: 1rem;
}
.room-stats {
display: flex;
gap: 1rem;
font-size: 0.875rem;
color: var(--text-light);
}
/* Captcha */
.captcha-box {
background: var(--bg);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
text-align: center;
}
.captcha-question {
font-size: 1.5rem;
font-weight: 600;
color: var(--primary);
margin-bottom: 0.5rem;
}
.captcha-input {
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>
</head>
<body>
<header class="header">
<div class="logo">🍽️ Reservierung</div>
<nav class="nav">
<span id="userStatus"></span>
<button class="btn btn-secondary" id="loginBtn" onclick="showLogin()">Admin Login</button>
<button class="btn btn-primary admin-only" onclick="showNewBooking()">+ Buchung</button>
</nav>
</header>
<!-- Login Modal -->
<div class="modal" id="loginModal">
<div class="modal-content">
<h2 class="modal-title">Admin Login</h2>
<div style="background:#fef3c7;border:1px solid #fcd34d;padding:0.75rem;border-radius:6px;margin-bottom:1rem;font-size:0.85rem;color:#92400e">
🔐 Standard-Passwort: <strong>changeme</strong> Bitte nach erstem Login ändern!
</div>
<form id="loginForm" onsubmit="handleLogin(event)">
<div class="form-group">
<label>Passwort</label>
<input type="password" id="loginPassword" required placeholder="••••••••">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%">Anmelden</button>
<button type="button" class="btn btn-secondary" style="width: 100%; margin-top: 0.5rem" onclick="hideLogin()">Abbrechen</button>
</form>
</div>
</div>
<main class="container">
<div class="guest-notice" id="guestNotice">
👋 Willkommen! Als Gast können Sie Tische reservieren. Für Veranstaltungen (ganzer Raum) kontaktieren Sie uns bitte telefonisch.
</div>
<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">
<label>Telefon</label>
<input type="tel" id="bookingPhone" placeholder="+49 123 456789">
</div>
<div class="form-group">
<label>Datum</label>
<input type="date" id="bookingDate" required>
</div>
<div class="form-group">
<label>Zeit</label>
<input type="time" id="bookingTime" required>
</div>
<div class="form-group">
<label>Personen</label>
<input type="number" id="bookingGuests" min="1" max="20" value="2" required>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%">Reservierung anfragen</button>
</form>
</div>
</main>
<script>
let currentUser = null;
let selectedRoom = null;
let selectedTable = null;
// Check session on load
async function checkSession() {
try {
const res = await fetch('/api/session');
const data = await res.json();
if (data.logged_in) {
currentUser = data;
document.body.classList.add('admin-visible');
document.getElementById('loginBtn').textContent = 'Abmelden';
document.getElementById('loginBtn').onclick = logout;
document.getElementById('guestNotice').style.display = 'none';
}
} catch (e) {
console.log('Not logged in');
}
loadRooms();
loadCaptcha();
}
// Load rooms
async function loadRooms() {
try {
const res = await fetch('/api/rooms');
const rooms = await res.json();
displayRooms(rooms);
} catch (e) {
document.getElementById('loading').textContent = 'Fehler beim Laden der Räume';
}
}
function displayRooms(rooms) {
const grid = document.getElementById('roomsGrid');
const loading = document.getElementById('loading');
loading.style.display = 'none';
grid.style.display = 'grid';
grid.innerHTML = rooms.map(room => `
<div class="room-card" onclick="selectRoom(${room.id}, '${room.name}')" style="border-left: 4px solid ${room.color}">
<div class="room-header">
<span class="room-name">${room.name}</span>
<span class="room-badge" style="background: ${room.color}20; color: ${room.color}">
${room.capacity} Plätze
</span>
</div>
<div class="room-capacity">
${room.areas ? room.areas.length : 0} Bereiche ·
${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('');
}
function selectRoom(roomId, roomName) {
selectedRoom = roomId;
document.getElementById('bookingSection').style.display = 'block';
document.getElementById('bookingSection').scrollIntoView({ behavior: 'smooth' });
}
// Captcha
async function loadCaptcha() {
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';
}
}
// Login
function showLogin() {
document.getElementById('loginModal').classList.add('active');
}
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 = {
name: document.getElementById('bookingName').value,
phone: document.getElementById('bookingPhone').value,
date: document.getElementById('bookingDate').value,
time_from: document.getElementById('bookingTime').value,
guests: parseInt(document.getElementById('bookingGuests').value),
captcha_token: captchaToken,
captcha_answer: captchaAnswer
};
try {
const res = await fetch('/api/reservations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (res.ok) {
alert('Reservierung erfolgreich! Wir melden uns bei Ihnen.');
document.getElementById('bookingForm').reset();
loadCaptcha();
} else {
const err = await res.json();
alert(err.error || 'Fehler bei der Reservierung');
}
} catch (e) {
alert('Netzwerkfehler');
}
}
// Set today's date as default
document.getElementById('bookingDate').valueAsDate = new Date();
// Initialize
checkSession();
</script>
</body>
</html>