feat: add booking time ranges and repeating series
This commit is contained in:
1 parent
59feca2f8e
commit
d433d271d0
29 files changed
+2135
-180
No files matched your search
@@ -0,0 +1,41 @@
|
||||
{#
|
||||
The shared body of both booking modals. Which fields exist is decided by the controller
|
||||
(the edit modal removes date and the recurrence fields), so everything here is guarded with
|
||||
`is defined` rather than duplicated into two templates.
|
||||
|
||||
Show/hide is Stimulus, not htmx: toggling a fieldset needs no server round trip, and the
|
||||
house rule keeps purely client-side behaviour out of htmx.
|
||||
#}
|
||||
<div {{ stimulus_controller('bookingform') }}>
|
||||
{% if form.date is defined %}
|
||||
{{ form_row(form.date) }}
|
||||
{% endif %}
|
||||
|
||||
<div class="pt-4">
|
||||
{{ form_row(form.allDay, { attr: { 'data-action': 'bookingform#toggleTimes' } }) }}
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 pt-2 {{ form.allDay.vars.data ? 'hidden' : '' }}"
|
||||
{{ stimulus_target('bookingform', 'times') }}>
|
||||
{{ form_row(form.startTime) }}
|
||||
{{ form_row(form.endTime) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if form.repeat is defined %}
|
||||
<div class="pt-4 border-t border-gray-200 mt-4">
|
||||
{{ form_row(form.repeat, { attr: { 'data-action': 'bookingform#toggleRepeat' } }) }}
|
||||
|
||||
<div class="pt-2 {{ form.repeat.vars.data is null ? 'hidden' : '' }}"
|
||||
{{ stimulus_target('bookingform', 'repeat') }}>
|
||||
<div class="{{ form.repeat.vars.data is null or form.repeat.vars.data.value != 'weekly' ? 'hidden' : '' }}"
|
||||
{{ stimulus_target('bookingform', 'weekdays') }}>
|
||||
{{ form_row(form.weekdays, { row_attr: { class: 'pb-2' } }) }}
|
||||
</div>
|
||||
{{ form_row(form.until) }}
|
||||
<p class="pt-1 text-xs text-gray-500">
|
||||
Belegte Tage werden übersprungen und danach aufgelistet.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -26,7 +26,7 @@
|
||||
{{ resource.type.label }} · {{ resource.label }}
|
||||
</p>
|
||||
|
||||
{{ form_row(form.date) }}
|
||||
{% include 'booking/_fields.html.twig' %}
|
||||
|
||||
<div class="flex justify-end gap-2 pt-6">
|
||||
<button type="button" class="button button--neutral" {{ stimulus_action('modal', 'close') }}>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{#
|
||||
Editing one occurrence. For a booking that belongs to a series this is the "override": only
|
||||
this day moves, the rest of the series is untouched and the occurrence is flagged as
|
||||
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.
|
||||
#}
|
||||
{% extends 'htmx_modal.html.twig' %}
|
||||
|
||||
{% block title %}{{ resource.label }} ändern{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ form_start(form, { attr: {
|
||||
'hx-post': form.vars.action,
|
||||
'hx-target': '#htmx-modal',
|
||||
'hx-swap': 'outerHTML',
|
||||
} }) }}
|
||||
{{ form_errors(form) }}
|
||||
|
||||
<p class="pb-4 text-sm text-gray-500">
|
||||
{{ resource.type.label }} · {{ booking.date|format_datetime(pattern: 'EEEE, d. MMMM y') }}
|
||||
</p>
|
||||
|
||||
{% if booking.partOfSeries %}
|
||||
<div class="mb-4 rounded-md bg-gray-50 px-4 py-3 text-sm">
|
||||
Teil einer Serie: {{ booking.series.describe }}.
|
||||
<span class="text-gray-500">Eine Änderung hier gilt nur für diesen Tag.</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% 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. #}
|
||||
<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">
|
||||
Freigeben
|
||||
</button>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button type="button" class="button button--neutral" {{ stimulus_action('modal', 'close') }}>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button type="submit" class="button button--primary">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
@@ -6,20 +6,51 @@
|
||||
<h1>Meine Buchungen</h1>
|
||||
<p class="subline">Alles, was ab heute für dich reserviert ist.</p>
|
||||
|
||||
{% 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.resource.label }}</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ 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>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="pt-8">Termine</h2>
|
||||
|
||||
{% if bookings is empty %}
|
||||
<p class="mt-8 text-sm text-gray-500">
|
||||
<p class="text-sm text-gray-500">
|
||||
Noch nichts gebucht. <a class="underline" href="{{ path('app_dashboard') }}">Zum Raumplan</a>.
|
||||
</p>
|
||||
{% else %}
|
||||
<ul class="mt-8 divide-y divide-gray-200 border-y border-gray-200">
|
||||
<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.date|format_datetime(pattern: 'EEEE, d. MMMM y') }}
|
||||
<span class="font-normal text-gray-500">· {{ booking.timeRange }}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ booking.resource.type.label }} · {{ booking.resource.label }}
|
||||
{% 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">
|
||||
|
||||
Reference in new issue
Block a user