feat: add admin views, a booking horizon and confirmation modals
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
8caf618950
commit
1a3640b15c
27 files changed
+1269
-62
No files matched your search
@@ -0,0 +1,52 @@
|
||||
{#
|
||||
What an administrator sees when clicking an occupied shape on the floor plan.
|
||||
|
||||
Not the edit modal: a shape can hold several bookings on one day once part-day spans exist,
|
||||
so "the booking" has no single referent -- and an administrator releases rather than
|
||||
reschedules. Each row posts to the ordinary app_booking_release route, whose ownership check
|
||||
admits an admin, so there is still one code path that removes a booking.
|
||||
#}
|
||||
{% extends 'htmx_modal.html.twig' %}
|
||||
|
||||
{% block title %}{{ resource.label }} verwalten{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p class="pb-4 text-sm text-gray-500">
|
||||
{{ resource.type.label }} · {{ date|format_datetime(pattern: 'EEEE, d. MMMM y') }}
|
||||
{%- if resource.group %} · {{ resource.group.label }}{% endif %}
|
||||
</p>
|
||||
|
||||
{% if bookings is empty %}
|
||||
<p class="text-sm text-gray-500">In diesem Zeitraum liegt hier keine Buchung.</p>
|
||||
{% else %}
|
||||
<ul class="divide-y divide-gray-200 border-y border-gray-200">
|
||||
{% for booking in bookings %}
|
||||
<li class="flex items-center justify-between gap-4 py-3">
|
||||
<div>
|
||||
<div class="font-semibold">{{ booking.user.name }}</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ booking.timeRange }}
|
||||
{% if booking.partOfSeries %}
|
||||
<span class="badge bg-gray-100 text-gray-600 ml-1">Serie</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{# Same as the edit modal: opens the shared confirmation modal over this one.
|
||||
Confirming repaints the board out of band and removes both. #}
|
||||
<button type="button" class="button button--small button--warning"
|
||||
hx-get="{{ path('app_booking_release', { id: booking.id }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
Freigeben
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex justify-end pt-6">
|
||||
<button type="button" class="button button--neutral" {{ stimulus_action('modal', 'close') }}>
|
||||
Schließen
|
||||
</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,104 @@
|
||||
{#
|
||||
Every booking, not just yours. The point of the screen is the "Freigeben" button on somebody
|
||||
else's row: when a colleague calls in sick, their desk has to go back into the pool.
|
||||
|
||||
Filtering is a plain GET form -- the house rule hands navigation to the browser. The two
|
||||
destructive actions open the shared confirmation modal via htmx and then navigate back here,
|
||||
which is what ?return=admin says; without it a confirmed release swaps board fragments this
|
||||
page does not have.
|
||||
#}
|
||||
{% extends 'layout.html.twig' %}
|
||||
|
||||
{% block title %}Verwaltung · ephub{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<a class="text-sm text-gray-500 hover:text-gray-900" href="{{ path('app_dashboard') }}">← Übersicht</a>
|
||||
|
||||
<h1 class="pt-2">Verwaltung</h1>
|
||||
<p class="subline">Buchungen aller Mitarbeitenden.</p>
|
||||
|
||||
{{ form_start(form, { attr: { class: 'mt-6 rounded-md border border-gray-200 p-4' } }) }}
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{{ form_row(form.from) }}
|
||||
{{ form_row(form.to) }}
|
||||
{{ form_row(form.type) }}
|
||||
{{ form_row(form.user) }}
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 pt-2">
|
||||
<a class="button button--neutral" href="{{ path('app_admin_booking') }}">Zurücksetzen</a>
|
||||
<button type="submit" class="button button--primary">Filtern</button>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
|
||||
{% if series is not empty %}
|
||||
<h2 class="pt-8">Serien</h2>
|
||||
<ul class="divide-y divide-gray-200 border-y border-gray-200">
|
||||
{% for entry in series %}
|
||||
<li class="flex items-center justify-between gap-4 py-3">
|
||||
<div>
|
||||
<div class="font-semibold">
|
||||
{{ entry.user.name }}
|
||||
<span class="font-normal text-gray-500">· {{ entry.resource.label }}</span>
|
||||
{%- if entry.resource.group %} <span class="font-normal text-gray-500">· {{ entry.resource.group.label }}</span>{% endif %}
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ entry.describe }} · bis {{ entry.endDate|format_datetime(pattern: 'd. MMMM y') }}
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="button button--small button--neutral"
|
||||
hx-get="{{ path('app_series_end', { id: entry.id, return: 'admin' }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
Serie beenden
|
||||
</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="pt-8">Termine</h2>
|
||||
<p class="subline">
|
||||
{{ filter.fromDate|format_datetime(pattern: 'd. MMMM y') }} bis
|
||||
{{ filter.toDate|format_datetime(pattern: 'd. MMMM y') }}
|
||||
</p>
|
||||
|
||||
{% if bookings is empty %}
|
||||
<p class="text-sm text-gray-500">Keine Buchungen in diesem Zeitraum.</p>
|
||||
{% else %}
|
||||
<ul class="divide-y divide-gray-200 border-y border-gray-200">
|
||||
{% for booking in bookings %}
|
||||
<li class="flex items-center justify-between gap-4 py-3">
|
||||
<div>
|
||||
<div class="font-semibold">
|
||||
{{ booking.user.name }}
|
||||
<span class="font-normal text-gray-500">
|
||||
· {{ booking.date|format_datetime(pattern: 'EEE, d. MMM y') }}
|
||||
· {{ booking.timeRange }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ booking.resource.type.label }} · {{ booking.resource.label }}
|
||||
{%- if booking.resource.group %} · {{ booking.resource.group.label }}{% endif %}
|
||||
{% if booking.partOfSeries %}
|
||||
<span class="badge bg-gray-100 text-gray-600 ml-1">Serie</span>
|
||||
{% endif %}
|
||||
{% if booking.overridden %}
|
||||
<span class="badge bg-amber-100 text-amber-800 ml-1">abweichend</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<a class="text-sm underline shrink-0"
|
||||
href="{{ path('app_board', { type: booking.resource.type.value, date: booking.date|date('Y-m-d') }) }}">Im Plan</a>
|
||||
<button type="button" class="button button--small button--neutral"
|
||||
hx-get="{{ path('app_booking_release', { id: booking.id, return: 'admin' }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
Freigeben
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user