Files
myep/templates/booking/_participant_form.html.twig
T
2026-03-16 11:59:11 +01:00

351 lines
18 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% import _self as macros %}
{# Macro to render a field or placeholder with consistent fieldset structure #}
{% macro service_field(form, fieldName, label, options = {}, undefinedLabel = 'Nicht wählbar') %}
{% if form[fieldName] is defined %}
{{ form_row(form[fieldName], options) }}
{% else %}
<fieldset class="mb-1">
<legend class="font-semibold mb-1">{{ label }}</legend>
<div class="text-sm text-gray-500">{{ undefinedLabel }}</div>
</fieldset>
{% endif %}
{% endmacro %}
{# Specialized macro for checkbox fields (like rental insurance) that need manual fieldset wrapping #}
{% macro checkbox_field(form, fieldName, label, options = {}, undefinedLabel = 'Nicht wählbar') %}
{% if form[fieldName] is defined %}
<fieldset class="mb-1">
<legend class="font-semibold mb-1">{{ label }}</legend>
{{ form_row(form[fieldName], options) }}
</fieldset>
{% else %}
<fieldset class="mb-1">
<legend class="font-semibold mb-1">{{ label }}</legend>
{% if undefinedLabel %}
<div class="text-sm text-gray-500">{{ undefinedLabel }}</div>
{% endif %}
</fieldset>
{% endif %}
{% endmacro %}
{# Standalone participant form view (replaces main content area) #}
{% block participant_form %}
{% import _self as macros %}
<div id="participant-form-view">
<h2>{{ participantIndex == 0 ? 'Anmelder:in' : 'Teilnehmer:in ' ~ (participantIndex + 1) }}</h2>
{{ form_start(form, {
'attr': {
'novalidate': 'novalidate',
'hx-post': path(submitRouteName, submitRouteParams|default({index: participantIndex})),
'hx-target': '#main-content',
'hx-swap': 'innerHTML'
}
}) }}
<div id="participant-form" class="space-y-4">
{# Personal data section #}
<div class="grid grid-cols-2 gap-4">
{{ form_row(form.firstName) }}
{{ form_row(form.lastName) }}
{{ form_row(form.dateOfBirth, {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content',
'hx-swap': 'innerHTML'
}
}) }}
{{ form_row(form.gender) }}
{{ form_row(form.nationality) }}
</div>
{# Contact information #}
<div class="grid grid-cols-2 gap-4">
{{ form_row(form.email) }}
{{ form_row(form.mobile) }}
</div>
{# Address #}
{% if form.address is defined %}
<div class="grid grid-cols-2 gap-4">
{{ form_row(form.address.street) }}
{{ form_row(form.address.postCode) }}
{{ form_row(form.address.city) }}
{{ form_row(form.address.country) }}
</div>
{% endif %}
{# Room assignment #}
<div class="grid grid-cols-2 gap-4">
{% if form.assignedRoomId is defined %}
{{ form_row(form.assignedRoomId, {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content',
'hx-swap': 'innerHTML'
}
}) }}
{% else %}
{# Display room label directly when only one room type selected (auto-assigned) #}
<div>
<label class="font-semibold mb-1 block">Zimmer</label>
<div class="text-sm text-gray-600">{{ bookingDto.singleRoomLabel }}</div>
</div>
{% endif %}
{% if form.remarksRoom is defined %}
{{ form_row(form.remarksRoom) }}
{% endif %}
</div>
{# Eligibility checks #}
{% set participantData = form.vars.data.participant %}
{% set hasDateOfBirth = participantData and participantData.dateOfBirth %}
{% set isEligible = hasDateOfBirth and is_participant_eligible(bookingDto, participantIndex) %}
{% if not hasDateOfBirth %}
<div class="my-4 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div class="flex items-center">
<svg class="w-5 h-5 text-blue-600 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path>
</svg>
<p class="text-sm text-blue-800">
Leistungen sind erst nach Angabe des Geburtsdatums buchbar
</p>
</div>
</div>
{% elseif not isEligible %}
<div class="my-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<div class="flex items-center">
<svg class="w-5 h-5 text-red-600 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path>
</svg>
<p class="text-sm text-red-800">
Buchung wegen des Alters von Teilnehmer:in {{ participantIndex + 1 }} nicht möglich
</p>
</div>
</div>
{% endif %}
{# Service selection #}
{% if isEligible %}
<div class="grid grid-cols-2 gap-4">
{{ macros.service_field(form, 'skiPass', 'Skipass', {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}) }}
{{ macros.service_field(form, 'courses', 'Kurse', {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}) }}
{{ macros.service_field(form, 'additionalServices', 'Zusatzleistungen', {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}) }}
{{ macros.service_field(form, 'board', 'Verpflegung', {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}) }}
<div>
{{ macros.service_field(form, 'rentals', 'Leihmaterial', {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}, 'Bitte zuerst den Skipass auswählen') }}
{{ macros.checkbox_field(form, 'rentalInsurance', null, {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}, null) }}
</div>
{% if form.bodyDimensions is defined %}
<div class="grid gap-y-4">
{{ form_row(form.bodyDimensions.height) }}
{{ form_row(form.bodyDimensions.shoeSize) }}
{{ form_row(form.bodyDimensions.weight) }}
</div>
{% else %}
<div>{# empty slot to maintain grid layout #}</div>
{% endif %}
<div class="col-span-2">
{# Insurance display for edit mode (read-only) #}
{% if bookingDto.mode == constant('App\\Form\\Model\\BookingDto::MODE_EDIT') and form.vars.data.participant.insurance %}
<fieldset class="mb-1">
<legend class="font-semibold mb-1">Reiseversicherung</legend>
<div class="text-sm text-gray-600">
{{ form.vars.data.participant.insurance.label }}
{% if form.vars.data.participant.insurance.price and form.vars.data.participant.insurance.price > 0 %}
<span class="text-gray-500">(€{{ form.vars.data.participant.insurance.price|number_format(2, ',', '.') }})</span>
{% endif %}
</div>
</fieldset>
{# Insurance field OR assigned insurance display for dependent participants (create mode) #}
{% else %}
{% set showBulkInsurance = participantIndex > 0 and bookingDto.participants[0].bulkInsuranceBooking %}
{% if showBulkInsurance %}
<fieldset class="mb-1">
<legend class="font-semibold mb-1">Reiseversicherung</legend>
<div class="text-sm text-gray-600">
{% set applicantInsurance = bookingDto.participants[0].insurance %}
{% if applicantInsurance %}
{{ applicantInsurance.label }}
{% if applicantInsurance.price and applicantInsurance.price > 0 %}
<span class="text-gray-500">(€{{ applicantInsurance.price|number_format(2, ',', '.') }})</span>
{% endif %}
<span class="italic text-gray-500 ml-2"> wie Anmelder</span>
{% else %}
<span class="italic">wie Anmelder</span>
{% endif %}
</div>
</fieldset>
{% else %}
<fieldset class="mb-1">
<legend class="font-semibold mb-1">Reiseversicherung</legend>
{# Bulk insurance booking checkbox (applicant only) #}
{% if form.bulkInsuranceBooking is defined %}
{{ form_row(form.bulkInsuranceBooking) }}
{% endif %}
{% if form.insurance is defined %}
{{ form_row(form.insurance, {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
},
'label': false
}) }}
{% else %}
<div class="text-sm text-gray-500">Nicht wählbar</div>
{% endif %}
</fieldset>
{% endif %}
{% endif %}
</div>
</div>
{# Transportation Services Section #}
<div class="mt-6 border-t pt-4">
<h3 class="font-semibold text-lg mb-4">Anreise</h3>
<div class="grid grid-cols-2 gap-4">
{% if form.transportationOutbound is defined %}
{{ form_row(form.transportationOutbound, {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}) }}
{% endif %}
{% if form.transportationInbound is defined %}
{{ form_row(form.transportationInbound, {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}) }}
{% endif %}
{% if form.pickup is defined %}
<div class="col-span-2 mt-4">
{{ form_row(form.pickup, {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}) }}
</div>
{% endif %}
{% if form.parking is defined %}
<div class="mt-4">
{{ form_row(form.parking, {
'attr': {
'hx-trigger': 'change',
'hx-post': path(refreshRouteName, refreshRouteParams|default({index: participantIndex})),
'hx-target': '#main-content', 'hx-swap': 'innerHTML'
}
}) }}
</div>
{% endif %}
{% if form.licensePlate is defined %}
<div class="mt-4">
{{ form_row(form.licensePlate) }}
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
<div class="flex justify-between mt-8">
{% if cancelRouteName is defined %}
<button type="button"
class="button bg-button bg-button--secondary"
hx-get="{{ path(cancelRouteName, cancelRouteParams|default({})) }}"
hx-target="#main-content"
hx-swap="innerHTML"
{{ qa_attribute('btn-cancel') }}>
Abbrechen
</button>
{% else %}
<button type="button"
class="button bg-button bg-button--secondary"
hx-get="{{ path('app_booking_create_step_2') }}"
hx-target="#main-content"
hx-swap="innerHTML"
{{ qa_attribute('btn-cancel') }}>
Abbrechen
</button>
{% endif %}
<button type="submit" class="button bg-button bg-button--secondary" {{ qa_attribute('btn-submit') }}>
Speichern
</button>
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
{% endblock %}
{# Sidebar summary with conditional OOB swap #}
{% block booking_summary %}
<div id="booking-summary"{% if htmx_oob_swap|default(false) %} hx-swap-oob="true"{% endif %}>
{% include 'booking/_summary.html.twig' with {
'bookingCreateDto': bookingDto,
'participantCount': summaryData.participantsCount,
'groupedSelectedRooms': summaryData.groupedSelectedRooms,
'assignmentCounts': summaryData.assignmentCounts,
'pricingData': pricingData
} %}
</div>
{% endblock %}