feat: 4-tier role concept, personal balance & cash register system, left off-canvas drawer navigation

This commit is contained in:
2026-09-22 09:05:14 +02:00
parent 0bdcbe7976
commit 896f1d7d08
8 changed files with 1350 additions and 257 deletions
+195 -47
View File
@@ -16,52 +16,184 @@
</head>
<body>
<!-- ================= TOP NAVBAR ================= -->
<header class="navbar">
<div class="container nav-content">
<a href="/" class="brand">
🥨 <span>Schmankerl</span> <small class="tenant-badge">{{ tenant.name if tenant else 'Portal' }}</small>
</a>
<!-- LEFT: Menu Toggle + Brand -->
<div class="nav-left">
<button class="menu-toggle-btn" id="drawerToggleBtn" onclick="openDrawer()" aria-label="Menü öffnen" aria-expanded="false">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
<span class="menu-btn-text">Menü</span>
</button>
<div class="nav-right-cluster">
<!-- Theme Switcher -->
<a href="/" class="brand">
🥨 <span>Schmankerl</span>
{% if tenant and tenant.name %}
<small class="tenant-badge">{{ tenant.name.replace('Schmankerl ', '') }}</small>
{% endif %}
</a>
</div>
<!-- RIGHT: Balance (if logged in) + Theme Toggle + User Chip -->
<div class="nav-right">
{% if current_user and current_user.role != 'guest' %}
<a href="/kasse" class="user-balance-pill" title="Aktuelles Guthaben – Klick für Kassendetails">
<span class="balance-icon">💰</span>
<span class="balance-amount">{{ "%.2f"|format(current_user.balance) }} €</span>
</a>
{% endif %}
<!-- Theme Toggle -->
<button type="button" class="theme-toggle-btn" id="themeToggleBtn" onclick="toggleTheme()" title="Design umschalten (Hell / Dunkel)">
<span id="themeToggleIcon">🌙</span> <span id="themeToggleLabel" class="hide-mobile">Dunkel</span>
</button>
<!-- Mobile Hamburger Toggle -->
<button class="mobile-toggle" aria-label="Menü öffnen" id="mobileNavToggle" aria-expanded="false">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
{% if current_user %}
<button type="button" class="user-chip-btn" id="userChipBtn" onclick="openDrawer()" title="Profil & Menü öffnen">
<span class="user-avatar-circle">{{ current_user.first_name[0] }}</span>
<span class="user-chip-name hide-mobile">{{ current_user.first_name }}</span>
</button>
{% else %}
<a href="/login" class="btn-primary btn-login-top">Anmelden</a>
{% endif %}
</div>
<nav class="nav-links" id="navLinks">
{% if current_user %}
<a href="/" class="nav-item">🥨 Bestellen</a>
<a href="/einkauf" class="nav-item nav-einkauf-link">🛒 Einkaufsliste & Kasse</a>
{% if current_user.role == 'admin' %}
<a href="/admin" class="nav-item nav-admin">⚙️ Orga & Admin</a>
{% endif %}
<span class="user-greeting">👤 {{ current_user.first_name }}</span>
<a href="/logout" class="nav-item nav-logout">Abmelden</a>
{% else %}
<a href="/login" class="nav-item nav-login">Anmelden</a>
{% endif %}
<a href="https://new.dev.waiswuaschd.de" target="_blank" class="nav-item nav-home">Zur Plattform</a>
</nav>
</div>
</header>
<!-- ================= LEFT NAVIGATION DRAWER ================= -->
<div class="drawer-backdrop" id="drawerBackdrop" onclick="closeDrawer()"></div>
<aside class="nav-drawer" id="navDrawer" aria-hidden="true">
<div class="drawer-header">
<div class="drawer-brand">
<span class="drawer-logo">🥨</span>
<div>
<strong style="font-size: 1.1rem; color: var(--text-main);">Schmankerl</strong>
<div class="drawer-tenant">{{ tenant.name if tenant else 'Brotzeit-Portal' }}</div>
</div>
</div>
<button type="button" class="drawer-close-btn" onclick="closeDrawer()" aria-label="Menü schließen">✕</button>
</div>
<!-- User Profile Card in Drawer -->
{% if current_user %}
<div class="drawer-user-card">
<div class="user-card-top">
<div class="user-card-avatar">{{ current_user.first_name[0] }}{{ current_user.last_name[0] if current_user.last_name else '' }}</div>
<div class="user-card-meta">
<div class="user-card-name">{{ current_user.first_name }} {{ current_user.last_name }}</div>
<div class="user-card-email">{{ current_user.email }}</div>
<div class="user-card-role">
{% if current_user.role == 'admin' %}
<span class="badge-role admin">👑 Administrator</span>
{% elif current_user.role == 'organizer' %}
<span class="badge-role organizer">📋 Organisator</span>
{% elif current_user.role == 'guest' %}
<span class="badge-role guest">🥨 Gast</span>
{% else %}
<span class="badge-role employee">👤 Mitarbeiter</span>
{% endif %}
</div>
</div>
</div>
{% if current_user.role != 'guest' %}
<div class="drawer-balance-box">
<div>
<div class="balance-box-label">Guthaben-Stand</div>
<div class="balance-box-value">{{ "%.2f"|format(current_user.balance) }} €</div>
</div>
<a href="/kasse" class="balance-box-link" onclick="closeDrawer()">Kasse & Verlauf →</a>
</div>
{% endif %}
</div>
{% endif %}
<!-- Navigation Items -->
<nav class="drawer-nav">
<div class="nav-group-title">BROTZEIT</div>
<a href="/" class="drawer-nav-item" onclick="closeDrawer()">
<span class="nav-item-icon">🥨</span>
<span class="nav-item-text">Brotzeit bestellen</span>
</a>
{% if current_user and (current_user.role in ['admin', 'organizer']) %}
<a href="/einkauf" class="drawer-nav-item" onclick="closeDrawer()">
<span class="nav-item-icon">🛒</span>
<span class="nav-item-text">Einkaufsliste & Kasse</span>
<span class="nav-badge-pill">Einkauf</span>
</a>
{% endif %}
{% if current_user and current_user.role != 'guest' %}
<div class="nav-group-title">KASSE & GUTHABEN</div>
<a href="/kasse" class="drawer-nav-item" onclick="closeDrawer()">
<span class="nav-item-icon">💰</span>
<span class="nav-item-text">Kassensystem & Guthaben</span>
</a>
{% endif %}
{% if current_user and current_user.role in ['admin', 'organizer'] %}
<div class="nav-group-title">ORGANISATION</div>
<a href="/admin?tab=event" class="drawer-nav-item" onclick="closeDrawer()">
<span class="nav-item-icon">📅</span>
<span class="nav-item-text">Termine & Runden</span>
</a>
<a href="/admin?tab=products" class="drawer-nav-item" onclick="closeDrawer()">
<span class="nav-item-icon">🥨</span>
<span class="nav-item-text">Sortiment & Preise</span>
</a>
<a href="/admin?tab=team" class="drawer-nav-item" onclick="closeDrawer()">
<span class="nav-item-icon">✉️</span>
<span class="nav-item-text">Einladungen & Gast-Links</span>
</a>
{% endif %}
{% if current_user and current_user.role == 'admin' %}
<div class="nav-group-title">SYSTEM-ADMINISTRATION</div>
<a href="/admin?tab=team" class="drawer-nav-item" onclick="closeDrawer()">
<span class="nav-item-icon">👥</span>
<span class="nav-item-text">Team- & Rollenverwaltung</span>
<span class="nav-badge-pill admin">Admin</span>
</a>
{% endif %}
<div class="nav-group-title">KONTO</div>
{% if current_user %}
<a href="/logout" class="drawer-nav-item nav-logout" onclick="closeDrawer()">
<span class="nav-item-icon">🚪</span>
<span class="nav-item-text">Abmelden</span>
</a>
{% else %}
<a href="/login" class="drawer-nav-item" onclick="closeDrawer()">
<span class="nav-item-icon">🔑</span>
<span class="nav-item-text">Anmelden</span>
</a>
{% endif %}
</nav>
<div class="drawer-footer">
<button type="button" class="drawer-theme-btn" onclick="toggleTheme()">
<span id="drawerThemeIcon">🌙</span> <span id="drawerThemeLabel">Dunkelmodus</span>
</button>
</div>
</aside>
<!-- ================= MAIN CONTENT ================= -->
<main class="main-content">
{% block content %}{% endblock %}
</main>
<!-- ================= FOOTER ================= -->
<footer class="footer">
<div class="container footer-inner">
<p>&copy; 2026 Schmankerl by <a href="https://new.dev.waiswuaschd.de">Waiswuaschd.de</a></p>
<p>&copy; 2026 Schmankerl – Das moderne Brotzeit-Portal</p>
<div class="footer-tenant-info">
Mandant: <strong>{{ tenant.slug if tenant else 'demo' }}</strong> ({{ tenant.subdomain if tenant else '' }})
Mandant: <strong>{{ tenant.name if tenant else 'demo' }}</strong>
</div>
</div>
</footer>
@@ -71,12 +203,18 @@
function updateThemeUI(theme) {
const icon = document.getElementById('themeToggleIcon');
const label = document.getElementById('themeToggleLabel');
const dIcon = document.getElementById('drawerThemeIcon');
const dLabel = document.getElementById('drawerThemeLabel');
if (theme === 'dark') {
if (icon) icon.innerText = '☀️';
if (label) label.innerText = 'Hell';
if (dIcon) dIcon.innerText = '☀️';
if (dLabel) dLabel.innerText = 'Hellmodus umschalten';
} else {
if (icon) icon.innerText = '🌙';
if (label) label.innerText = 'Dunkel';
if (dIcon) dIcon.innerText = '🌙';
if (dLabel) dLabel.innerText = 'Dunkelmodus umschalten';
}
}
@@ -88,30 +226,40 @@
updateThemeUI(next);
}
// Initialize Theme UI on load
// Drawer Open / Close Logic
function openDrawer() {
const drawer = document.getElementById('navDrawer');
const backdrop = document.getElementById('drawerBackdrop');
const btn = document.getElementById('drawerToggleBtn');
if (drawer && backdrop) {
drawer.classList.add('open');
backdrop.classList.add('open');
drawer.setAttribute('aria-hidden', 'false');
if (btn) btn.setAttribute('aria-expanded', 'true');
document.body.style.overflow = 'hidden';
}
}
function closeDrawer() {
const drawer = document.getElementById('navDrawer');
const backdrop = document.getElementById('drawerBackdrop');
const btn = document.getElementById('drawerToggleBtn');
if (drawer && backdrop) {
drawer.classList.remove('open');
backdrop.classList.remove('open');
drawer.setAttribute('aria-hidden', 'true');
if (btn) btn.setAttribute('aria-expanded', 'false');
document.body.style.overflow = '';
}
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeDrawer();
});
document.addEventListener('DOMContentLoaded', () => {
const current = document.documentElement.getAttribute('data-theme') || 'light';
updateThemeUI(current);
// Mobile Nav Drawer Toggle
const navToggle = document.getElementById('mobileNavToggle');
const navLinks = document.getElementById('navLinks');
if (navToggle && navLinks) {
navToggle.addEventListener('click', () => {
const isOpen = navLinks.classList.toggle('open');
navToggle.classList.toggle('open', isOpen);
navToggle.setAttribute('aria-expanded', isOpen);
document.body.style.overflow = isOpen ? 'hidden' : '';
});
navLinks.querySelectorAll('a').forEach(link => {
link.addEventListener('click', () => {
navLinks.classList.remove('open');
navToggle.classList.remove('open');
navToggle.setAttribute('aria-expanded', 'false');
document.body.style.overflow = '';
});
});
}
});
</script>
</body>