Files
reservierungssystem/app-backup-20260516-1208/templates/index.html
T

637 lines
21 KiB
HTML

<!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;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a2e;
color: #fff;
min-height: 100vh;
}
.header {
background: #16213e;
padding: 15px 30px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #2d3a5c;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: #00d4aa;
}
.nav {
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 {
background: #00d4aa;
color: #000;
border: none;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
}
.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;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 500;
}
.badge-confirmed {
background: rgba(0,212,170,0.2);
color: #00d4aa;
}
.badge-pending {
background: rgba(241,196,15,0.2);
color: #f1c40f;
}
.badge-cancelled {
background: rgba(231,76,60,0.2);
color: #e74c3c;
}
.booking-number {
font-family: monospace;
background: rgba(0,212,170,0.1);
padding: 4px 8px;
border-radius: 4px;
color: #00d4aa;
}
.hidden {
display: none !important;
}
/* Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background: #16213e;
border: 1px solid #2d3a5c;
border-radius: 12px;
padding: 30px;
width: 90%;
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
}
.modal h2 {
margin-bottom: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #888;
}
.form-group input, .form-group select, .form-group textarea {
width: 100%;
padding: 12px;
border: 1px solid #2d3a5c;
background: #1a1a2e;
color: #fff;
border-radius: 6px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.modal-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 25px;
}
.btn-secondary {
background: #333;
color: #fff;
}
.btn-secondary:hover {
background: #444;
}
@media (max-width: 1024px) {
.dashboard-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
.header {
flex-direction: column;
gap: 15px;
}
.nav {
flex-wrap: wrap;
justify-content: center;
}
.form-row {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="header">
<div class="logo">🍽️ Reservierungssystem</div>
<div class="nav">
<button class="nav-btn active" onclick="showView('dashboard')">Dashboard</button>
<button class="nav-btn" onclick="showView('reservations')">Reservierungen</button>
<button class="nav-btn" onclick="showView('floorplan')">Tischplan</button>
<button class="nav-btn" onclick="showView('guests')">Gäste</button>
<button class="nav-btn" onclick="showView('emails')">E-Mails</button>
</div>
</div>
<div class="container">
<!-- Dashboard View -->
<div id="view-dashboard">
<div class="dashboard-grid">
<div class="stat-card">
<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">
<label>Datum *</label>
<input type="date" id="res-date" required>
</div>
<div class="form-group">
<label>Uhrzeit *</label>
<input type="time" id="res-time" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Name *</label>
<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 class="form-row">
<div class="form-group">
<label>Telefon</label>
<input type="tel" id="res-phone">
</div>
<div class="form-group">
<label>E-Mail</label>
<input type="email" id="res-email">
</div>
</div>
<div class="form-group">
<label>Quelle</label>
<select id="res-source">
<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 class="form-group" id="phone-caller-group">
<label>Anrufer (bei Telefon-Buchung)</label>
<input type="text" id="res-caller">
</div>
<div class="form-group">
<label>Notizen</label>
<textarea id="res-notes" rows="3"></textarea>
</div>
</form>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="closeModal()">Abbrechen</button>
<button class="btn" onclick="saveReservation()">Speichern</button>
</div>
</div>
</div>
<script>
// Global state
let currentView = 'dashboard';
// Init
document.addEventListener('DOMContentLoaded', () => {
loadDashboard();
document.getElementById('res-date').valueAsDate = new Date();
// Source change handler
document.getElementById('res-source').addEventListener('change', (e) => {
const callerGroup = document.getElementById('phone-caller-group');
callerGroup.style.display = e.target.value === 'phone' ? 'block' : 'none';
});
});
// View switching
function showView(view) {
// Hide all views
document.querySelectorAll('[id^="view-"]').forEach(el => {
el.classList.add('hidden');
});
// 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
async function api(endpoint, options = {}) {
const res = await fetch(`/api${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
return res.json();
}
// Dashboard
async function loadDashboard() {
const data = await api('/dashboard');
document.getElementById('stat-today').textContent = data.today_count;
document.getElementById('stat-guests').textContent = data.guests_today;
document.getElementById('stat-available').textContent = data.total_tables - data.today_count;
document.getElementById('stat-pending').textContent = data.pending_emails;
// Today's reservations
const tbody = document.getElementById('today-table-body');
tbody.innerHTML = data.today_reservations.map(r => `
<tr>
<td><span class="booking-number">${r.booking_number}</span></td>
<td>${r.time_from}</td>
<td>${r.guest_name || r.phone || 'Unbekannt'}</td>
<td>${r.guests}</td>
<td><span class="badge badge-${r.status}">${r.status}</span></td>
<td><button class="btn btn-secondary" onclick="viewReservation(${r.id})">Details</button></td>
</tr>
`).join('');
}
// Reservations
async function loadReservations() {
const date = document.getElementById('filter-date').value;
const params = date ? `?date=${date}` : '';
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
function openNewReservation() {
document.getElementById('modal-reservation').classList.remove('hidden');
}
function closeModal() {
document.getElementById('modal-reservation').classList.add('hidden');
document.getElementById('reservation-form').reset();
}
async function saveReservation() {
const data = {
date: document.getElementById('res-date').value,
time_from: document.getElementById('res-time').value,
guests: parseInt(document.getElementById('res-guests').value),
name: document.getElementById('res-name').value,
phone: document.getElementById('res-phone').value,
email: document.getElementById('res-email').value,
source: document.getElementById('res-source').value,
phone_caller_name: document.getElementById('res-caller').value,
notes: document.getElementById('res-notes').value
};
const result = await api('/reservations', {
method: 'POST',
body: JSON.stringify(data)
});
if (result.booking_number) {
alert(`Reservierung erstellt: ${result.booking_number}`);
closeModal();
loadDashboard();
} else {
alert('Fehler: ' + (result.error || 'Unbekannter Fehler'));
}
}
// Placeholder functions
function loadFloorplan() { console.log('Floorplan loading...'); }
function loadGuests() { console.log('Guests loading...'); }
function loadEmails() { console.log('Emails loading...'); }
function filterEmails(status) { console.log('Filter:', status); }
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>
</body>
</html>