feat: panel layout for the accommodation booking edit and detail pages
This commit is contained in:
1 parent
5fdee68ccd
commit
eb45693257
9 files changed
+531
-398
No files matched your search
@@ -22,155 +22,200 @@
|
||||
{% form_theme form 'forms_admin.html.twig' %}
|
||||
|
||||
{{ form_start(form) }}
|
||||
<div class="grid lg:grid-cols-2 gap-y-4 lg:gap-x-8">
|
||||
<h2 class="lg:col-span-2 text-base font-bold">Kontaktdaten</h2>
|
||||
{{ form_row(form.groupName) }}
|
||||
{{ form_row(form.salutation) }}
|
||||
{{ form_row(form.firstName) }}
|
||||
{{ form_row(form.lastName) }}
|
||||
{{ form_row(form.email) }}
|
||||
{{ form_row(form.phone) }}
|
||||
{{ form_row(form.street) }}
|
||||
{{ form_row(form.zip) }}
|
||||
{{ form_row(form.city) }}
|
||||
{{ form_errors(form) }}
|
||||
{# Ordered by what gets negotiated: the stay first, the contact data last. The sidebar keeps
|
||||
status, price and the save button in view on wide screens. #}
|
||||
<div class="xl:grid xl:grid-cols-3 xl:gap-6 xl:items-start space-y-6 xl:space-y-0">
|
||||
<div class="xl:col-span-2 space-y-6">
|
||||
<twig:form:section title="Aufenthalt">
|
||||
<div class="grid sm:grid-cols-2 gap-4">
|
||||
{{ form_row(form.dateFrom) }}
|
||||
{{ form_row(form.dateTo) }}
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-3 gap-4 pt-4">
|
||||
{{ form_row(form.paxCount) }}
|
||||
{{ form_row(form.minorsCount) }}
|
||||
{{ form_row(form.childrenCount) }}
|
||||
</div>
|
||||
|
||||
<h2 class="lg:col-span-2 text-base font-bold pt-2">Reisedaten</h2>
|
||||
{{ form_row(form.dateFrom) }}
|
||||
{{ form_row(form.dateTo) }}
|
||||
{{ form_row(form.paxCount) }}
|
||||
{{ form_row(form.minorsCount) }}
|
||||
{{ form_row(form.childrenCount) }}
|
||||
|
||||
{# Saved by their own modals, which reprice the booking on the spot — see SplitPeriodController. #}
|
||||
<div class="lg:col-span-2">
|
||||
<h3 class="font-bold pb-1">Zeiträume</h3>
|
||||
<p class="text-xs text-gray-500 pb-2">
|
||||
Anreise und Abreise umfassen den ganzen Aufenthalt. Abgeteilte Zeiträume, etwa für ein
|
||||
Vorausteam, werden mit eigenen Positionen berechnet; Personen und Leistungen oben gelten
|
||||
nur für den Zeitraum der Gruppe.
|
||||
</p>
|
||||
<table class="w-full text-sm">
|
||||
{% if booking.leadingPeriod %}
|
||||
{% set p = booking.leadingPeriod %}
|
||||
{{ block('period_row') }}
|
||||
{% endif %}
|
||||
<tr class="border-t border-gray-200">
|
||||
<td class="py-1 whitespace-nowrap">{{ booking.mainDateFrom | date('d.m.Y') }} – {{ booking.mainDateTo | date('d.m.Y') }}</td>
|
||||
<td class="py-1 whitespace-nowrap">{{ booking.mainNights }} {{ booking.mainNights != 1 ? 'Nächte' : 'Nacht' }}</td>
|
||||
<td class="py-1" colspan="2">Gruppe</td>
|
||||
<td class="py-1 text-right">
|
||||
{# One period per side, so only the free sides are offered. #}
|
||||
{% if booking.leadingPeriod is null or booking.trailingPeriod is null %}
|
||||
<twig:dropdown>
|
||||
{% if booking.leadingPeriod is null %}
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_split', back_params | merge({ position: 'leading' })) }}">
|
||||
am Anfang abteilen
|
||||
</twig:dropdown:hxbutton>
|
||||
{% endif %}
|
||||
{% if booking.trailingPeriod is null %}
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_split', back_params | merge({ position: 'trailing' })) }}">
|
||||
am Ende abteilen
|
||||
</twig:dropdown:hxbutton>
|
||||
{% endif %}
|
||||
</twig:dropdown>
|
||||
{# Saved by their own modals, which reprice the booking on the spot — see SplitPeriodController. #}
|
||||
<div class="mt-6 border-t border-gray-200 pt-4">
|
||||
<div class="font-bold pb-1">Zeiträume</div>
|
||||
<p class="text-xs text-gray-500 pb-2">
|
||||
Anreise und Abreise umfassen den ganzen Aufenthalt. Abgeteilte Zeiträume, etwa für ein
|
||||
Vorausteam, werden mit eigenen Positionen berechnet; Personen und Leistungen gelten
|
||||
nur für den Zeitraum der Gruppe.
|
||||
</p>
|
||||
<table class="w-full text-sm">
|
||||
{% if booking.leadingPeriod %}
|
||||
{% include 'admin/accommodation_booking/_period_row.html.twig' with { period: booking.leadingPeriod } %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if booking.trailingPeriod %}
|
||||
{% set p = booking.trailingPeriod %}
|
||||
{{ block('period_row') }}
|
||||
<tr class="border-t border-gray-200">
|
||||
<td class="py-1 whitespace-nowrap">{{ booking.mainDateFrom | date('d.m.Y') }} – {{ booking.mainDateTo | date('d.m.Y') }}</td>
|
||||
<td class="py-1 whitespace-nowrap">{{ booking.mainNights }} {{ booking.mainNights != 1 ? 'Nächte' : 'Nacht' }}</td>
|
||||
<td class="py-1" colspan="2">Gruppe</td>
|
||||
<td class="py-1 text-right">
|
||||
{# One period per side, so only the free sides are offered. #}
|
||||
{% if booking.leadingPeriod is null or booking.trailingPeriod is null %}
|
||||
<twig:dropdown>
|
||||
{% if booking.leadingPeriod is null %}
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_split', back_params | merge({ position: 'leading' })) }}">
|
||||
am Anfang abteilen
|
||||
</twig:dropdown:hxbutton>
|
||||
{% endif %}
|
||||
{% if booking.trailingPeriod is null %}
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_split', back_params | merge({ position: 'trailing' })) }}">
|
||||
am Ende abteilen
|
||||
</twig:dropdown:hxbutton>
|
||||
{% endif %}
|
||||
</twig:dropdown>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if booking.trailingPeriod %}
|
||||
{% include 'admin/accommodation_booking/_period_row.html.twig' with { period: booking.trailingPeriod } %}
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</twig:form:section>
|
||||
|
||||
{% if form.boardService is defined or form.selectedAdditionalServices is defined %}
|
||||
<twig:form:section title="Leistungen">
|
||||
<div class="space-y-4">
|
||||
{% if form.boardService is defined %}
|
||||
{{ form_row(form.boardService) }}
|
||||
{% endif %}
|
||||
{% if form.selectedAdditionalServices is defined %}
|
||||
<div>
|
||||
{{ form_label(form.selectedAdditionalServices) }}
|
||||
{# Rows written out here rather than through the theme's choice_widget_expanded,
|
||||
whose first:pt-0 would shift only the first cell of the two columns. #}
|
||||
<div class="grid sm:grid-cols-2 gap-x-8">
|
||||
{% for child in form.selectedAdditionalServices %}
|
||||
<div class="relative flex items-start py-1">
|
||||
<div class="min-w-0 flex-1 text-sm leading-4">
|
||||
<label for="{{ child.vars.id }}" class="select-none font-medium text-gray-900">
|
||||
{{ child.vars.label }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="ml-3 flex h-4 items-center">
|
||||
{{ form_widget(child) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ form_errors(form.selectedAdditionalServices) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</twig:form:section>
|
||||
{% endif %}
|
||||
|
||||
<twig:form:section title="Rabatte">
|
||||
<div class="grid sm:grid-cols-3 gap-4">
|
||||
{{ form_row(form.accommodationDiscount) }}
|
||||
{{ form_row(form.boardServiceDiscount) }}
|
||||
{{ form_row(form.additionalServicesDiscount) }}
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-3 gap-4 pt-4">
|
||||
{{ form_row(form.totalDiscountLabel, { row_attr: { class: 'sm:col-span-2' } }) }}
|
||||
{{ form_row(form.totalDiscountAmount) }}
|
||||
</div>
|
||||
</twig:form:section>
|
||||
|
||||
<twig:form:section title="Kontakt">
|
||||
<div class="grid sm:grid-cols-2 gap-4">
|
||||
{{ form_row(form.groupName, { row_attr: { class: 'sm:col-span-2' } }) }}
|
||||
{{ form_row(form.salutation) }}
|
||||
<div class="hidden sm:block"></div>
|
||||
{{ form_row(form.firstName) }}
|
||||
{{ form_row(form.lastName) }}
|
||||
{{ form_row(form.email) }}
|
||||
{{ form_row(form.phone) }}
|
||||
{{ form_row(form.street, { row_attr: { class: 'sm:col-span-2' } }) }}
|
||||
{{ form_row(form.zip) }}
|
||||
{{ form_row(form.city) }}
|
||||
</div>
|
||||
</twig:form:section>
|
||||
|
||||
<twig:form:section title="Bemerkungen">
|
||||
{{ form_widget(form.remarks) }}
|
||||
{{ form_errors(form.remarks) }}
|
||||
</twig:form:section>
|
||||
</div>
|
||||
|
||||
<aside class="space-y-6 xl:sticky xl:top-8">
|
||||
<twig:form:section title="Status">
|
||||
{# Read-only: the status only ever moves through a named action on the detail page,
|
||||
so that it cannot be set without the email and timestamp that belong to it. #}
|
||||
<dl class="grid grid-cols-2 gap-y-1 text-sm">
|
||||
<dt class="text-gray-500">Status</dt>
|
||||
<dd>{% include 'admin/accommodation_booking/_status_label.html.twig' %}</dd>
|
||||
<dt class="text-gray-500">Herkunft</dt>
|
||||
<dd>{{ booking.origin.label }}</dd>
|
||||
{% if booking.acceptedAt is not null %}
|
||||
<dt class="text-gray-500">Angenommen</dt>
|
||||
<dd>{{ booking.acceptedAt | date('d.m.Y, H:i') }}</dd>
|
||||
{% endif %}
|
||||
{% if booking.confirmedAt is not null %}
|
||||
<dt class="text-gray-500">Bestätigt</dt>
|
||||
<dd>{{ booking.confirmedAt | date('d.m.Y, H:i') }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
{% if form.managedBy is defined %}
|
||||
<div class="pt-4">
|
||||
{{ form_row(form.managedBy) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</twig:form:section>
|
||||
|
||||
{% if form.boardService is defined or form.selectedAdditionalServices is defined %}
|
||||
<h2 class="lg:col-span-2 text-base font-bold pt-2">Leistungen</h2>
|
||||
{% if form.boardService is defined %}
|
||||
{{ form_row(form.boardService) }}
|
||||
{% if priceBreakdown is not null %}
|
||||
{% set currency = booking.pricingCurrency ?? priceBreakdown.currency %}
|
||||
<twig:form:section title="Preis">
|
||||
<table class="w-full text-sm">
|
||||
{% if priceBreakdown.periods is not empty %}
|
||||
<tr>
|
||||
<td class="py-1">Gruppe</td>
|
||||
<td class="py-1 text-right whitespace-nowrap">{{ ((priceBreakdown.total - priceBreakdown.periodsTotal) / 100) | format_currency(currency) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-1">Zeiträume</td>
|
||||
<td class="py-1 text-right whitespace-nowrap">{{ (priceBreakdown.periodsTotal / 100) | format_currency(currency) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if priceBreakdown.discountedTotal != priceBreakdown.total %}
|
||||
{% if priceBreakdown.periods is empty %}
|
||||
<tr>
|
||||
<td class="py-1">Preis</td>
|
||||
<td class="py-1 text-right whitespace-nowrap">{{ (priceBreakdown.total / 100) | format_currency(currency) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr class="text-green-700">
|
||||
<td class="py-1">Rabatte</td>
|
||||
<td class="py-1 text-right whitespace-nowrap">– {{ ((priceBreakdown.total - priceBreakdown.discountedTotal) / 100) | format_currency(currency) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr class="border-t border-gray-200 font-bold">
|
||||
<td class="pt-2">Gesamtpreis</td>
|
||||
<td class="pt-2 text-right whitespace-nowrap">{{ (priceBreakdown.discountedTotal / 100) | format_currency(currency) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p class="text-xs text-gray-500 pt-2">
|
||||
Stand der letzten Speicherung. Änderungen oben werden erst beim Speichern neu berechnet.
|
||||
</p>
|
||||
</twig:form:section>
|
||||
{% endif %}
|
||||
{% if form.selectedAdditionalServices is defined %}
|
||||
{{ form_row(form.selectedAdditionalServices) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<h2 class="lg:col-span-2 text-base font-bold pt-2">Status & Rabatt</h2>
|
||||
{# Read-only: the status only ever moves through a named action on the detail page,
|
||||
so that it cannot be set without the email and timestamp that belong to it. #}
|
||||
<p class="lg:col-span-2 text-sm">
|
||||
<span class="font-medium">Status:</span>
|
||||
{% include 'admin/accommodation_booking/_status_label.html.twig' %}
|
||||
<span class="text-gray-500">· Herkunft: {{ booking.origin.label }}</span>
|
||||
</p>
|
||||
{% if booking.acceptedAt is not null %}
|
||||
<p class="lg:col-span-2 text-xs text-gray-500">
|
||||
Angenommen am {{ booking.acceptedAt | date('d.m.Y, H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if booking.confirmedAt is not null %}
|
||||
<p class="lg:col-span-2 text-xs text-gray-500">
|
||||
Bestätigt am {{ booking.confirmedAt | date('d.m.Y, H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if form.managedBy is defined %}
|
||||
<div class="lg:col-span-2">
|
||||
{{ form_row(form.managedBy) }}
|
||||
<div class="flex flex-col gap-3">
|
||||
<button type="submit" class="button button--primary button--small w-full">
|
||||
speichern
|
||||
</button>
|
||||
<a href="{{ path('app_admin_accommodationbooking_show', back_params) }}" class="text-sm text-gray-500 hover:text-gray-900 hover:underline">
|
||||
← zurück ohne speichern
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="lg:col-span-2 grid lg:grid-cols-3 gap-y-4 lg:gap-x-8">
|
||||
{{ form_row(form.accommodationDiscount) }}
|
||||
{{ form_row(form.boardServiceDiscount) }}
|
||||
{{ form_row(form.additionalServicesDiscount) }}
|
||||
</div>
|
||||
<div class="lg:col-span-2 grid lg:grid-cols-2 gap-y-4 lg:gap-x-8">
|
||||
{{ form_row(form.totalDiscountLabel) }}
|
||||
{{ form_row(form.totalDiscountAmount) }}
|
||||
</div>
|
||||
<div class="lg:col-span-2">
|
||||
{{ form_row(form.remarks) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between pt-8">
|
||||
<a href="{{ path('app_admin_accommodationbooking_show', back_params) }}" class="button button--secondary button--small">
|
||||
zurück
|
||||
</a>
|
||||
<button type="submit" class="button button--primary button--small">
|
||||
speichern
|
||||
</button>
|
||||
</aside>
|
||||
</div>
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block period_row %}
|
||||
{% set period_params = { id: p.id } %}
|
||||
{% if app.request.query.get('r') %}
|
||||
{% set period_params = period_params | merge({ r: app.request.query.get('r') }) %}
|
||||
{% endif %}
|
||||
<tr class="border-t border-gray-200 align-top">
|
||||
<td class="py-1 whitespace-nowrap">{{ p.dateFrom | date('d.m.Y') }} – {{ p.dateTo | date('d.m.Y') }}</td>
|
||||
<td class="py-1 whitespace-nowrap">{{ p.nights }} {{ p.nights != 1 ? 'Nächte' : 'Nacht' }}</td>
|
||||
<td class="py-1">
|
||||
{% if p.label %}
|
||||
<div class="font-medium">{{ p.label }}</div>
|
||||
{% endif %}
|
||||
{% for line in p.lines %}
|
||||
<div>{{ line.label }}</div>
|
||||
{% else %}
|
||||
<div class="text-gray-500">keine Positionen</div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="py-1 text-right whitespace-nowrap">
|
||||
{{ (p.total / 100) | format_currency(booking.pricingCurrency ?? booking.accommodation.currency) }}
|
||||
</td>
|
||||
<td class="py-1 text-right">
|
||||
<twig:dropdown>
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_edit', period_params) }}">
|
||||
bearbeiten
|
||||
</twig:dropdown:hxbutton>
|
||||
<twig:dropdown:hxbutton url="{{ path('app_admin_accommodationbooking_period_remove', period_params) }}" warning>
|
||||
entfernen
|
||||
</twig:dropdown:hxbutton>
|
||||
</twig:dropdown>
|
||||
</td>
|
||||
</tr>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user