style: remove all emojis, apply saas minimal dark mode design system

This commit is contained in:
DanielS
2026-08-19 00:36:47 +02:00
parent d69a9b52a8
commit 52b0728510
5 changed files with 165 additions and 86 deletions

View File

@@ -3,95 +3,125 @@ import { useAppStore } from './store/useAppStore';
import FloorPlan from './components/FloorPlan'; import FloorPlan from './components/FloorPlan';
import Timeline from './components/Timeline'; import Timeline from './components/Timeline';
import ReservationList from './components/ReservationList'; import ReservationList from './components/ReservationList';
import BookingModal from './components/BookingModal'; import ReservationModal from './components/BookingModal';
import { ViewMode } from './types'; import { ViewMode } from './types';
const NAV_ITEMS: { id: ViewMode; label: string; icon: string }[] = [ const NAV = [
{ id: 'floor', label: '2D Tischplan', icon: '⬛' }, { id: 'floor' as ViewMode, label: 'Tischplan' },
{ id: 'timeline', label: 'Timeline', icon: '📊' }, { id: 'timeline' as ViewMode, label: 'Timeline' },
{ id: 'list', label: 'Reservierungen', icon: '📋' }, { id: 'list' as ViewMode, label: 'Reservierungen' },
]; ];
function App() { export default function App() {
const [view, setView] = useState<ViewMode>('floor'); const [view, setView] = useState<ViewMode>('floor');
const { tables, reservations, showBookingModal, openBookingModal } = useAppStore(); const { tables, reservations, showBookingModal, openBookingModal } = useAppStore();
const activeCount = reservations.filter((r) => ['PENDING', 'CONFIRMED', 'ARRIVED'].includes(r.status)).length;
const freeCount = tables.filter((t) => t.status === 'FREE').length; const freeCount = tables.filter((t) => t.status === 'FREE').length;
const activeCount = reservations.filter((r) => ['PENDING', 'CONFIRMED', 'ARRIVED'].includes(r.status)).length;
return ( return (
<div className="min-h-screen bg-slate-950 text-slate-100" style={{ fontFamily: "'Inter', system-ui, sans-serif" }}> <div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh', background: 'var(--color-bg)' }}>
{/* Header */} {/* ── Header ── */}
<header className="sticky top-0 z-40 border-b border-slate-800/80 bg-slate-950/95 backdrop-blur-xl px-4 lg:px-6 py-3"> <header style={{
<div className="max-w-7xl mx-auto flex items-center justify-between gap-4"> position: 'sticky', top: 0, zIndex: 50,
{/* Brand */} background: 'rgba(9,9,13,0.92)',
<div className="flex items-center gap-3"> backdropFilter: 'blur(16px)',
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center font-black text-slate-950 text-sm shadow-lg shadow-amber-500/25"> borderBottom: '1px solid var(--color-border)',
PL padding: '0 1.5rem',
height: 56,
display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '1rem',
}}>
{/* Brand */}
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
<div style={{
width: 32, height: 32, borderRadius: 8,
background: 'var(--color-accent)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 12, fontWeight: 700, color: '#fff',
letterSpacing: '-0.5px',
boxShadow: '0 0 20px var(--color-accent-glow)',
}}>PL</div>
<div>
<div style={{ fontWeight: 600, fontSize: 14, color: 'var(--color-text)', letterSpacing: '-0.2px' }}>
Pizzeria Luigi
</div> </div>
<div className="hidden sm:block"> <div style={{ fontSize: 11, color: 'var(--color-muted)', display: 'flex', alignItems: 'center', gap: 5 }}>
<p className="font-bold text-base leading-tight">Pizzeria Luigi</p> <span style={{ width: 5, height: 5, borderRadius: '50%', background: 'var(--color-success)', display: 'inline-block' }} />
<p className="text-xs text-slate-500 flex items-center gap-1.5"> {new Date().toLocaleDateString('de-DE', { weekday: 'long', day: '2-digit', month: 'short' })}
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
Live · {new Date().toLocaleDateString('de-DE', { weekday: 'long', day: '2-digit', month: 'long' })}
</p>
</div> </div>
</div> </div>
{/* Stats */}
<div className="hidden md:flex items-center gap-4 text-xs">
<div className="flex items-center gap-2 px-3 py-1.5 bg-slate-900 rounded-xl border border-slate-800">
<span className="w-2 h-2 rounded-full bg-emerald-500" />
<span className="text-slate-400">Frei:</span>
<span className="font-bold text-emerald-400">{freeCount}</span>
</div>
<div className="flex items-center gap-2 px-3 py-1.5 bg-slate-900 rounded-xl border border-slate-800">
<span className="w-2 h-2 rounded-full bg-amber-400" />
<span className="text-slate-400">Aktiv:</span>
<span className="font-bold text-amber-400">{activeCount}</span>
</div>
</div>
{/* Nav */}
<nav className="flex bg-slate-900/80 p-1 rounded-xl border border-slate-800 gap-1">
{NAV_ITEMS.map((item) => (
<button
key={item.id}
onClick={() => setView(item.id)}
className={`px-3 py-1.5 rounded-lg text-xs font-semibold transition flex items-center gap-1.5 ${
view === item.id
? 'bg-amber-500 text-slate-950 shadow'
: 'text-slate-400 hover:text-slate-200'
}`}
>
<span>{item.icon}</span>
<span className="hidden sm:inline">{item.label}</span>
</button>
))}
</nav>
{/* CTA */}
<button
onClick={() => openBookingModal()}
className="bg-emerald-500 hover:bg-emerald-400 active:scale-95 text-slate-950 font-bold px-4 py-2 rounded-xl text-xs transition shadow-lg shadow-emerald-500/20 flex items-center gap-1.5"
>
<span>+</span>
<span className="hidden sm:inline">Walk-In</span>
</button>
</div> </div>
{/* Stats */}
<div style={{ display: 'flex', gap: '0.5rem' }}>
<Chip color="success" label="Frei" value={freeCount} />
<Chip color="accent" label="Aktiv" value={activeCount} />
</div>
{/* Nav */}
<nav style={{
display: 'flex', background: 'var(--color-surface)', borderRadius: 10,
border: '1px solid var(--color-border)', padding: 3, gap: 2,
}}>
{NAV.map((n) => (
<button
key={n.id}
onClick={() => setView(n.id)}
style={{
padding: '5px 14px', borderRadius: 7, fontSize: 12, fontWeight: 600, cursor: 'pointer',
border: 'none', transition: 'all 0.15s',
background: view === n.id ? 'var(--color-accent)' : 'transparent',
color: view === n.id ? '#fff' : 'var(--color-muted)',
boxShadow: view === n.id ? '0 2px 12px var(--color-accent-glow)' : 'none',
}}
>
{n.label}
</button>
))}
</nav>
{/* CTA */}
<button
onClick={() => openBookingModal()}
style={{
background: 'var(--color-accent)', color: '#fff',
border: 'none', borderRadius: 8, cursor: 'pointer',
padding: '7px 16px', fontSize: 12, fontWeight: 600,
display: 'flex', alignItems: 'center', gap: 6,
boxShadow: '0 2px 16px var(--color-accent-glow)',
transition: 'all 0.15s',
}}
onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--color-accent-hover)')}
onMouseLeave={(e) => (e.currentTarget.style.background = 'var(--color-accent)')}
>
Walk-In
</button>
</header> </header>
{/* Main */} {/* ── Content ── */}
<main className="max-w-7xl mx-auto p-4 lg:p-6"> <main style={{ flex: 1, maxWidth: 1280, width: '100%', margin: '0 auto', padding: '1.5rem' }}>
{view === 'floor' && <FloorPlan tables={tables} />} {view === 'floor' && <FloorPlan tables={tables} />}
{view === 'timeline' && <Timeline />} {view === 'timeline' && <Timeline />}
{view === 'list' && <ReservationList />} {view === 'list' && <ReservationList />}
</main> </main>
{/* Booking Modal */} {showBookingModal && <ReservationModal />}
{showBookingModal && <BookingModal />}
</div> </div>
); );
} }
export default App; // ─── Shared atoms ────────────────────────────────────────────────────────────
function Chip({ label, value, color }: { label: string; value: number; color: 'success' | 'accent' }) {
const c = color === 'success' ? 'var(--color-success)' : 'var(--color-accent)';
return (
<div style={{
display: 'flex', alignItems: 'center', gap: 6,
background: 'var(--color-surface)', borderRadius: 8,
border: '1px solid var(--color-border)', padding: '4px 10px', fontSize: 11,
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: c }} />
<span style={{ color: 'var(--color-muted)' }}>{label}</span>
<span style={{ fontWeight: 700, color: c }}>{value}</span>
</div>
);
}

View File

@@ -18,12 +18,12 @@ const DURATION_OPTIONS = [
]; ];
const EXTRA_TAGS = [ const EXTRA_TAGS = [
{ id: 'Hochstuhl', icon: '🪑', label: 'Hochstuhl' }, { id: 'Hochstuhl', label: 'Hochstuhl' },
{ id: 'Geburtstag', icon: '🎂', label: 'Geburtstag' }, { id: 'Geburtstag', label: 'Geburtstag' },
{ id: 'Fensterplatz', icon: '🪟', label: 'Fensterplatz' }, { id: 'Fensterplatz', label: 'Fensterplatz' },
{ id: 'Hund', icon: '🐕', label: 'Hund' }, { id: 'Hund', label: 'Hund' },
{ id: 'Terrasse', icon: '☀️', label: 'Terrasse / Außen' }, { id: 'Terrasse', label: 'Terrasse / Außen' },
{ id: 'Allergie', icon: '⚠️', label: 'Allergie' }, { id: 'Allergie', label: 'Allergie' },
]; ];
// ─── Helpers ──────────────────────────────────────────────────────────────── // ─── Helpers ────────────────────────────────────────────────────────────────
@@ -230,7 +230,6 @@ export default function ReservationModal() {
: 'bg-slate-800/50 border-slate-700 text-slate-400 hover:border-slate-500 hover:text-slate-300' : 'bg-slate-800/50 border-slate-700 text-slate-400 hover:border-slate-500 hover:text-slate-300'
}`} }`}
> >
<span className="text-base">{tag.icon}</span>
<span>{tag.label}</span> <span>{tag.label}</span>
</button> </button>
))} ))}

View File

@@ -62,7 +62,7 @@ export default function FloorPlan({ tables }: Props) {
cursor-pointer shadow-lg`} cursor-pointer shadow-lg`}
> >
<span className="font-bold text-xl">{t.number}</span> <span className="font-bold text-xl">{t.number}</span>
<span className="text-xs opacity-75">👤 {t.maxCapacity}</span> <span className="text-xs opacity-60">{t.maxCapacity} P.</span>
<span className="text-[10px] font-semibold uppercase tracking-wider opacity-80"> <span className="text-[10px] font-semibold uppercase tracking-wider opacity-80">
{STATUS_LABEL[t.status]} {STATUS_LABEL[t.status]}
</span> </span>

View File

@@ -27,11 +27,13 @@ const NEXT_STATUS: Partial<Record<ReservationStatus, ReservationStatus>> = {
ARRIVED: 'FINISHED', ARRIVED: 'FINISHED',
}; };
const TAG_ICONS: Record<string, string> = { const TAG_LABELS: Record<string, string> = {
Hochstuhl: '🪑', Hochstuhl: 'Hochstuhl',
Allergie: '⚠️', Allergie: 'Allergie',
Fensterplatz: '🪟', Fensterplatz: 'Fensterplatz',
Geburtstag: '🎂', Geburtstag: 'Geburtstag',
Hund: 'Hund',
Terrasse: 'Terrasse',
}; };
export default function ReservationList() { export default function ReservationList() {
@@ -54,17 +56,17 @@ export default function ReservationList() {
</span> </span>
</div> </div>
<div className="text-xs mt-1 text-current/70 flex flex-wrap gap-x-3 gap-y-0.5"> <div className="text-xs mt-1 text-current/70 flex flex-wrap gap-x-3 gap-y-0.5">
<span>👤 {r.guestCount} Pers.</span> <span>{r.guestCount} Pers.</span>
<span>🕐 {r.startTime}{r.endTime}</span> <span>{r.startTime}{r.endTime}</span>
<span>📋 Tisch {getTableNames(r.tableIds)}</span> <span>Tisch {getTableNames(r.tableIds)}</span>
{r.guest.phone && <span>📞 {r.guest.phone}</span>} {r.guest.phone && <span>{r.guest.phone}</span>}
</div> </div>
{r.tags.length > 0 && ( {r.tags.length > 0 && (
<div className="flex gap-1 mt-2 flex-wrap"> <div className="flex gap-1 mt-2 flex-wrap">
{r.tags.map((tag) => ( {r.tags.map((tag) => (
<span key={tag} className="text-xs px-2 py-0.5 rounded-full bg-slate-800/80 text-slate-300 border border-slate-700"> <span key={tag} className="text-xs px-2 py-0.5 rounded-full bg-slate-800/80 text-slate-300 border border-slate-700">
{TAG_ICONS[tag] ?? '🏷️'} {tag} {TAG_LABELS[tag] ?? tag}
</span> </span>
))} ))}
</div> </div>
)} )}

View File

@@ -1 +1,49 @@
@import "tailwindcss"; @import "tailwindcss";
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@theme {
--font-sans: 'Inter', system-ui, sans-serif;
--color-bg: #09090d;
--color-surface: #111116;
--color-surface-2: #1a1a24;
--color-border: #1f1f2e;
--color-border-2: #2a2a3a;
--color-accent: #6366f1;
--color-accent-hover: #818cf8;
--color-accent-dim: rgba(99,102,241,0.12);
--color-accent-glow: rgba(99,102,241,0.25);
--color-success: #22c55e;
--color-warning: #f59e0b;
--color-danger: #ef4444;
--color-sky: #38bdf8;
--color-text: #f4f4f8;
--color-muted: #71717a;
--color-subtle: #3f3f52;
}
*, *::before, *::after { box-sizing: border-box; }
html, body, #root {
height: 100%;
margin: 0;
background-color: var(--color-bg);
color: var(--color-text);
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
}
/* Scrollbar */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--color-border-2); border-radius: 3px; }
/* Selection */
::selection { background: var(--color-accent-dim); color: var(--color-text); }
/* Focus ring */
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }