feat: add the admin layout shell

This commit is contained in:
fromme committed 2026-09-13 14:31:40 +02:00
1 parent da8af9ff08
commit ad5c93049c
21 files changed
+441 -15

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
{# Flat inline alerts for now. myep renders these as Toastify toasts, but that means adopting
toastify-js as a dependency -- deferred until the toast/OOB work (plan step 7). #}
{% set levels = { error: 'bg-red-600', warning: 'bg-red-600', success: 'bg-emerald-600', info: 'bg-primary' } %}
{% for level, messages in app.flashes(levels|keys) %}
<div class="fixed bottom-4 right-4 z-50 space-y-2">
{% for message in messages %}
<div class="{{ levels[level] }} text-gray-100 rounded shadow-lg px-4 py-3 text-sm">
{{ message|trans }}
</div>
{% endfor %}
</div>
{% endfor %}
+25
View File
@@ -0,0 +1,25 @@
{# Plain nav instead of myep's knp-menu builder: a handful of links does not justify the
indirection. Active state is a prefix match on the current route name, which mirrors what
myep's menu matcher does with its 'routes' extras. #}
{% set current_route = app.request.attributes.get('_route') %}
{% set items = [
{ route: 'app_dashboard', label: 'Übersicht', icon: 'lucide:layout-grid' },
{ route: 'app_booking', label: 'Meine Buchungen', icon: 'lucide:calendar-days' },
] %}
<div class="min-h-full flex grow flex-col gap-y-5 lg:border-r border-gray-200 bg-white md:pr-6">
<nav class="flex flex-1 flex-col">
<ul role="list" class="space-y-1">
{% for item in items %}
{% set is_active = current_route starts with item.route %}
<li>
<a href="{{ path(item.route) }}"
class="group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold {{ is_active ? 'bg-gray-100 text-gray-900' : 'text-gray-700 hover:bg-gray-50' }}">
{{ ux_icon(item.icon, { class: 'h-5 w-5 shrink-0 ' ~ (is_active ? 'text-gray-600' : 'text-gray-400') }) }}
<span class="flex-1">{{ item.label }}</span>
</a>
</li>
{% endfor %}
</ul>
</nav>
</div>
+12 -13
View File
@@ -1,24 +1,23 @@
<!DOCTYPE html>
<html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
{% block stylesheets %}
{% endblock %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{# Internal tool -- keep it out of search engines. #}
<meta name="robots" content="noindex,nofollow">
<title>{% block title %}ephub{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.15em%22 font-size=%2296%22>🪑</text></svg>">
{# The Tailwind build is pulled in by assets/app.js's `import './styles/app.css'`,
so importmap() emits the <link> for it. This block is for anything outside that graph. #}
{% block stylesheets %}{% endblock %}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{% endblock %}
{% set frankenphp_hot_reload = app.request.server.get('FRANKENPHP_HOT_RELOAD') %}
{% if frankenphp_hot_reload %}
<meta name="frankenphp-hot-reload:url" content="{{ frankenphp_hot_reload }}">
<script src="https://cdn.jsdelivr.net/npm/idiomorph"></script>
<script src="https://cdn.jsdelivr.net/npm/frankenphp-hot-reload/+esm" type="module"></script>
{% endif %}
</head>
<body>
{# No hx-boost: htmx handles fragments only, the browser owns navigation. #}
<body class="bg-white text-gray-700 font-sans antialiased">
{% block body %}{% endblock %}
</body>
</html>
+7
View File
@@ -0,0 +1,7 @@
{% extends 'layout.html.twig' %}
{% block title %}Meine Buchungen · ephub{% endblock %}
{% block content %}
<h1>Meine Buchungen</h1>
{% endblock %}
+8
View File
@@ -0,0 +1,8 @@
{% extends 'layout.html.twig' %}
{% block title %}Übersicht · ephub{% endblock %}
{% block content %}
<h1>Übersicht</h1>
<p class="subline">Schreibtische und Parkplätze buchen.</p>
{% endblock %}
+43
View File
@@ -0,0 +1,43 @@
{% extends 'base.html.twig' %}
{% block body %}
<div class="max-w-screen-2xl mx-auto px-4 lg:px-8 lg:grid lg:grid-cols-4 gap-8 py-8">
<div class="lg:col-span-4 flex items-center justify-between pb-4 lg:pb-0">
<a href="{{ path('app_dashboard') }}" class="text-2xl font-black tracking-tight text-primary-dark">
ep<span class="text-pink">hub</span>
</a>
<button type="button"
class="lg:hidden"
{{ stimulus_controller('mobilenav', [], [], { 'mobilenav': '#mobilenav' }) }}
{{ stimulus_action('mobilenav', 'trigger', null, { 'mode': 'open' }) }}>
{{ ux_icon('lucide:menu', { class: 'w-8 h-8 text-gray-700' }) }}
</button>
</div>
<div class="hidden lg:block sticky top-0 h-screen">
{% include '_partials/_nav.html.twig' %}
</div>
<div class="lg:col-span-3">
{% block content %}{% endblock %}
</div>
</div>
<div id="mobilenav"
class="fixed z-50 top-0 left-0 inset-0 pr-16 bg-black/70 -translate-x-full"
{{ stimulus_controller('mobilenav', [], { 'closed': '-translate-x-full' }, { 'mobilenav': '#mobilenav' }) }}>
<div class="h-screen w-full bg-white">
<div class="flex justify-between p-8">
<span class="text-2xl font-black tracking-tight text-primary-dark">ep<span class="text-pink">hub</span></span>
<button type="button" {{ stimulus_action('mobilenav', 'trigger', null, { 'mode': 'close' }) }}>
{{ ux_icon('lucide:x', { class: 'w-8 h-8' }) }}
</button>
</div>
<div class="w-full h-full p-8 pt-0 overflow-y-scroll">
{% include '_partials/_nav.html.twig' %}
</div>
</div>
</div>
{% include '_partials/_flashes.html.twig' %}
{% endblock %}