feat: add admin views, a booking horizon and confirmation modals

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
frommeandClaude Opus 5 committed 2026-09-14 09:40:17 +02:00
1 parent 8caf618950
commit 1a3640b15c
27 files changed
+1269 -62

No files matched your search

+8 -8
View File
@@ -4,7 +4,8 @@
differing from it.
Releasing lives here too rather than on the floor plan, so the SVG needs no confirm dialogs
or inline CSRF tokens -- clicking your own desk always just opens this.
or inline CSRF tokens -- clicking your own desk always just opens this. The release button
opens the shared confirmation modal over this one.
#}
{% extends 'htmx_modal.html.twig' %}
@@ -32,14 +33,13 @@
{% include 'booking/_fields.html.twig' %}
<div class="flex flex-wrap justify-between gap-2 pt-6">
{# type="button" with its own hx-post: htmx issues the release request directly, so
this never submits the surrounding edit form and no nested <form> is needed. #}
{# type="button" with its own hx-get: htmx opens the confirmation modal over this
one, so nothing submits the surrounding edit form and no nested <form> is needed.
Confirming removes both modals -- see BookingController::boardResponse(). #}
<button type="button" class="button button--small button--warning"
hx-post="{{ path('app_booking_release', { id: booking.id }) }}"
hx-vals='{{ { _token: csrf_token('release-' ~ booking.id) }|json_encode }}'
hx-confirm="{{ resource.label }} am {{ booking.date|date('d.m.Y') }} freigeben?"
hx-target="#htmx-modal"
hx-swap="outerHTML">
hx-get="{{ path('app_booking_release', { id: booking.id }) }}"
hx-target="body"
hx-swap="beforeend">
Freigeben
</button>
@@ -0,0 +1,17 @@
{% extends 'htmx_confirmation_modal.html.twig' %}
{% block title %}Serie beenden?{% endblock %}
{% block content %}
<p>
{{ series.resource.label }} · {{ series.describe }}
{% if series.user.id != app.user.id %}
— Serie von <em>{{ series.user.name }}</em>
{% endif %}
</p>
<p class="pt-3 text-sm text-gray-500">
Alle Termine ab heute werden entfernt. Vergangene Termine bleiben als Historie stehen.
</p>
{% endblock %}
{% block button_confirm %}Serie beenden{% endblock %}
@@ -0,0 +1,22 @@
{% extends 'htmx_confirmation_modal.html.twig' %}
{% block title %}Buchung freigeben?{% endblock %}
{% block content %}
<p>
{{ booking.resource.type.label }} <em>{{ booking.resource.label }}</em>
{%- if booking.resource.group %} ({{ booking.resource.group.label }}){% endif %}
am {{ booking.date|format_datetime(pattern: 'EEEE, d. MMMM y') }}, {{ booking.timeRange }}
{% if not booking.belongsTo(app.user) %}
— gebucht von <em>{{ booking.user.name }}</em>
{% endif %}
</p>
{% if booking.partOfSeries %}
<p class="pt-3 text-sm text-gray-500">
Teil einer Serie: {{ booking.series.describe }}. Es wird nur dieser Tag freigegeben.
</p>
{% endif %}
{% endblock %}
{% block button_confirm %}Freigeben{% endblock %}
+16 -12
View File
@@ -22,12 +22,14 @@
{{ entry.describe }} · bis {{ entry.endDate|format_datetime(pattern: 'd. MMMM y') }}
</div>
</div>
{# Ends the series from today on; past occurrences are history and stay put. #}
<form method="post" action="{{ path('app_series_end', { id: entry.id }) }}"
onsubmit="return confirm('Serie ab heute beenden? Alle künftigen Termine werden entfernt.')">
<input type="hidden" name="_token" value="{{ csrf_token('end-series-' ~ entry.id) }}">
<button type="submit" class="button button--small button--neutral">Serie beenden</button>
</form>
{# Opens the confirmation modal; the CSRF token and the actual POST live in
there, which is why this row carries neither a form nor a token. #}
<button type="button" class="button button--small button--neutral"
hx-get="{{ path('app_series_end', { id: entry.id, return: 'bookings' }) }}"
hx-target="body"
hx-swap="beforeend">
Serie beenden
</button>
</li>
{% endfor %}
</ul>
@@ -62,12 +64,14 @@
<div class="flex items-center gap-3">
<a class="text-sm underline"
href="{{ path('app_board', { type: booking.resource.type.value, date: booking.date|date('Y-m-d') }) }}">Im Plan</a>
{# Ordinary form post: this page has no board fragments to swap, so it
reloads afterwards rather than pretending to be htmx. #}
<form method="post" action="{{ path('app_booking_release', { id: booking.id }) }}">
<input type="hidden" name="_token" value="{{ csrf_token('release-' ~ booking.id) }}">
<button type="submit" class="button button--small button--neutral">Freigeben</button>
</form>
{# ?return=bookings: this page has no board fragments to swap, so the
confirmed release navigates back here instead. #}
<button type="button" class="button button--small button--neutral"
hx-get="{{ path('app_booking_release', { id: booking.id, return: 'bookings' }) }}"
hx-target="body"
hx-swap="beforeend">
Freigeben
</button>
</div>
</li>
{% endfor %}