import React, { useState, useEffect, useMemo } from 'react'; import { Users, Check, ArrowLeft, Plus, Trash2, Settings, X, MessageSquare, AlertCircle } from 'lucide-react'; const KEY_EVENTS = 'reservation:events:v1'; const KEY_RESERVATIONS = 'reservation:reservations:v1'; const ADMIN_KEY = 'admin'; const DEFAULT_EVENTS = [ { id: 'e1', date: '2026-05-30', time: '19:00', title: 'NEON Chefs — Frühling', capacity: 12 }, { id: 'e2', date: '2026-06-06', time: '19:00', title: 'Pop-up Dinner', capacity: 14 }, { id: 'e3', date: '2026-06-20', time: '19:00', title: 'NEON Chefs — Sommer', capacity: 12 }, ]; // ─── Helpers ────────────────────────────────────────────────────────────────── function formatDate(iso) { const d = new Date(iso + 'T12:00:00'); return d.toLocaleDateString('de-CH', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', }); } function formatDateShort(iso) { const d = new Date(iso + 'T12:00:00'); const wd = d.toLocaleDateString('de-CH', { weekday: 'short' }).replace('.', ''); const day = d.getDate(); const mo = d.toLocaleDateString('de-CH', { month: 'short' }).replace('.', ''); return `${wd} ${day}. ${mo}`; } function uid() { return Math.random().toString(36).slice(2, 10) + Date.now().toString(36); } function todayISO() { return new Date().toISOString().slice(0, 10); } async function safeGet(key) { try { const r = await window.storage.get(key, true); return r ? JSON.parse(r.value) : null; } catch { return null; } } async function safeSet(key, value) { try { await window.storage.set(key, JSON.stringify(value), true); return true; } catch (e) { console.error(e); return false; } } // ─── App ────────────────────────────────────────────────────────────────────── export default function App() { const [view, setView] = useState('form'); const [loading, setLoading] = useState(true); const [events, setEvents] = useState([]); const [reservations, setReservations] = useState([]); const [lastReservation, setLastReservation] = useState(null); const [selectedEventId, setSelectedEventId] = useState(''); const [partySize, setPartySize] = useState(2); const [vorname, setVorname] = useState(''); const [nachname, setNachname] = useState(''); const [email, setEmail] = useState(''); const [phone, setPhone] = useState(''); const [comments, setComments] = useState(''); const [errors, setErrors] = useState({}); // Admin: type "admin" anywhere useEffect(() => { let buf = ''; const onKey = (e) => { if (e.key.length !== 1) return; buf = (buf + e.key.toLowerCase()).slice(-ADMIN_KEY.length); if (buf === ADMIN_KEY) setView('admin'); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, []); useEffect(() => { (async () => { let evts = await safeGet(KEY_EVENTS); if (!evts) { evts = DEFAULT_EVENTS; await safeSet(KEY_EVENTS, evts); } const resvs = (await safeGet(KEY_RESERVATIONS)) || []; setEvents(evts); setReservations(resvs); setLoading(false); })(); }, []); const bookedByEvent = useMemo(() => { const m = {}; for (const r of reservations) m[r.eventId] = (m[r.eventId] || 0) + Number(r.partySize || 0); return m; }, [reservations]); const upcomingEvents = useMemo(() => { const t = todayISO(); return events.filter((e) => e.date >= t) .sort((a, b) => a.date.localeCompare(b.date) || a.time.localeCompare(b.time)); }, [events]); const selectedEvent = events.find((e) => e.id === selectedEventId); const remainingForSelected = selectedEvent ? selectedEvent.capacity - (bookedByEvent[selectedEvent.id] || 0) : 0; function validate() { const errs = {}; if (!selectedEventId) errs.event = 'Bitte ein Datum wählen'; if (selectedEvent) { const remaining = selectedEvent.capacity - (bookedByEvent[selectedEvent.id] || 0); if (partySize < 1) errs.partySize = 'Mindestens 1 Person'; else if (partySize > remaining) errs.partySize = `Nur noch ${remaining} Plätze frei`; } if (!vorname.trim()) errs.vorname = 'Pflichtfeld'; if (!nachname.trim()) errs.nachname = 'Pflichtfeld'; if (!email.trim()) errs.email = 'Pflichtfeld'; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errs.email = 'Ungültige E-Mail'; if (!phone.trim()) errs.phone = 'Pflichtfeld'; setErrors(errs); return Object.keys(errs).length === 0; } async function handleSubmit() { if (!validate()) return; const reservation = { id: uid(), eventId: selectedEventId, eventDate: selectedEvent.date, eventTime: selectedEvent.time, eventTitle: selectedEvent.title, partySize: Number(partySize), vorname: vorname.trim(), nachname: nachname.trim(), email: email.trim(), phone: phone.trim(), comments: comments.trim(), createdAt: new Date().toISOString(), }; const next = [...reservations, reservation]; setReservations(next); await safeSet(KEY_RESERVATIONS, next); setLastReservation(reservation); setView('confirmed'); } function resetForm() { setSelectedEventId(''); setPartySize(2); setVorname(''); setNachname(''); setEmail(''); setPhone(''); setComments(''); setErrors({}); setLastReservation(null); setView('form'); } async function deleteReservation(id) { const next = reservations.filter((r) => r.id !== id); setReservations(next); await safeSet(KEY_RESERVATIONS, next); } async function addEvent(evt) { const next = [...events, { ...evt, id: uid() }]; setEvents(next); await safeSet(KEY_EVENTS, next); } async function deleteEvent(id) { if (!confirm('Anlass wirklich löschen?')) return; const next = events.filter((e) => e.id !== id); setEvents(next); await safeSet(KEY_EVENTS, next); } if (loading) { return (
Lädt…
); } return (
{view === 'form' && ( { setSelectedEventId(id); setErrors((e) => ({ ...e, event: undefined })); }} partySize={partySize} setPartySize={setPartySize} vorname={vorname} setVorname={setVorname} nachname={nachname} setNachname={setNachname} email={email} setEmail={setEmail} phone={phone} setPhone={setPhone} comments={comments} setComments={setComments} errors={errors} remainingForSelected={remainingForSelected} selectedEvent={selectedEvent} onSubmit={handleSubmit} onAdmin={() => setView('admin')} /> )} {view === 'confirmed' && lastReservation && } {view === 'admin' && ( setView('form')} onAddEvent={addEvent} onDeleteEvent={deleteEvent} onDeleteReservation={deleteReservation} /> )}
); } // ─── Reservation Form ───────────────────────────────────────────────────────── function ReservationForm(props) { const { events, bookedByEvent, selectedEventId, setSelectedEventId, partySize, setPartySize, vorname, setVorname, nachname, setNachname, email, setEmail, phone, setPhone, comments, setComments, errors, remainingForSelected, selectedEvent, onSubmit, onAdmin, } = props; return (
{/* Header */}

Reservation

Tisch reservieren

{/* Datum */}
{events.length === 0 ? (

Zurzeit sind keine Anlässe ausgeschrieben.

) : (
    {events.map((evt) => { const booked = bookedByEvent[evt.id] || 0; const remaining = evt.capacity - booked; const soldOut = remaining <= 0; const selected = evt.id === selectedEventId; return (
  • ); })}
)} {errors.event && }
{/* Personen */}
setPartySize(Math.max(1, Number(e.target.value) || 1))} className="num-input font-bold text-lg text-center w-12 bg-transparent border-0 outline-none" /> {partySize === 1 ? 'Person' : 'Personen'} {selectedEvent && ` · max. ${remainingForSelected} verfügbar`}
{errors.partySize && }
{/* Angaben */}
setVorname(e.target.value)} placeholder="Anna" /> setNachname(e.target.value)} placeholder="Müller" /> setEmail(e.target.value)} placeholder="anna@beispiel.ch" /> setPhone(e.target.value)} placeholder="+41 79 123 45 67" />