feat: comprehensive booking overview for confirmation
This commit is contained in:
@@ -41,4 +41,49 @@ class Pickup
|
|||||||
|
|
||||||
#[Groups(['api:booking'])]
|
#[Groups(['api:booking'])]
|
||||||
public array $mapping = [];
|
public array $mapping = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the formatted label for the pickup location without pricing.
|
||||||
|
*
|
||||||
|
* Creates user-friendly labels following the pattern:
|
||||||
|
* - Primary format: "City (Street)" if street is available
|
||||||
|
* - Fallback format: "City" if no street information
|
||||||
|
*
|
||||||
|
* @return string The formatted pickup location label
|
||||||
|
*/
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
$label = $this->city ?? '';
|
||||||
|
|
||||||
|
if (null !== $this->street && '' !== trim($this->street)) {
|
||||||
|
$label .= ' ('.$this->street.')';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the formatted label with pricing information.
|
||||||
|
*
|
||||||
|
* Formats the pickup label with price display:
|
||||||
|
* - Zero/null price: Just the label without price suffix
|
||||||
|
* - Positive price: "Label (€X,XX)" as surcharge
|
||||||
|
* - Negative price: "Label (-X,XX€ Rabatt)" as discount
|
||||||
|
*
|
||||||
|
* @return string The formatted pickup label with pricing
|
||||||
|
*/
|
||||||
|
public function getLabelWithPrice(): string
|
||||||
|
{
|
||||||
|
$label = $this->getLabel();
|
||||||
|
|
||||||
|
if (null === $this->price || 0.0 === $this->price) {
|
||||||
|
return $label;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->price < 0) {
|
||||||
|
return sprintf('%s (-%s€ Rabatt)', $label, number_format(abs($this->price), 2, ',', '.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('%s (€%s)', $label, number_format($this->price, 2, ',', '.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,4 +228,22 @@ class Travel
|
|||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a room by its ID.
|
||||||
|
*
|
||||||
|
* @param int|null $roomId The room ID to search for
|
||||||
|
*
|
||||||
|
* @return Room|null The room with the matching ID, or null if not found
|
||||||
|
*/
|
||||||
|
public function getRoomById(?int $roomId): ?Room
|
||||||
|
{
|
||||||
|
if (null === $roomId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rooms = $this->getRoomsByIds([$roomId]);
|
||||||
|
|
||||||
|
return false === empty($rooms) ? reset($rooms) : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -544,46 +544,13 @@ class ParticipantFieldOptionsProvider extends AbstractFieldOptionsProvider
|
|||||||
return $label;
|
return $label;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Format pickup labels with city and street information.
|
|
||||||
*
|
|
||||||
* Creates user-friendly labels for pickup locations following the existing pattern:
|
|
||||||
* - Primary format: "City (Street)" if street is available
|
|
||||||
* - Fallback format: "City" if no street information
|
|
||||||
*
|
|
||||||
* @param Pickup $pickup The pickup location to format
|
|
||||||
*
|
|
||||||
* @return string The formatted pickup location label
|
|
||||||
*/
|
|
||||||
private function formatPickupLabel(Pickup $pickup): string
|
|
||||||
{
|
|
||||||
$label = $pickup->city ?? '';
|
|
||||||
|
|
||||||
if (null !== $pickup->street && '' !== trim($pickup->street)) {
|
|
||||||
$label .= ' ('.$pickup->street.')';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $label;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function formatPickupLabelWithPrice(?Pickup $pickup): string
|
private function formatPickupLabelWithPrice(?Pickup $pickup): string
|
||||||
{
|
{
|
||||||
if (null === $pickup) {
|
if (null === $pickup) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$label = $this->formatPickupLabel($pickup);
|
return $pickup->getLabelWithPrice();
|
||||||
|
|
||||||
if (null === $pickup->price || 0.0 === $pickup->price) {
|
|
||||||
return $label;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($pickup->price < 0) {
|
|
||||||
// Negative prices are discounts
|
|
||||||
return sprintf('%s (-%s€ Rabatt)', $label, number_format(abs($pickup->price), 2, ',', '.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf('%s (€%s)', $label, number_format($pickup->price, 2, ',', '.'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
|
|
||||||
<h1>Neue Buchung</h1>
|
<h1>Neue Buchung</h1>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-8">
|
<div>
|
||||||
<div class="col-span-2">
|
|
||||||
<h2 class="mb-6">Buchung bestätigen</h2>
|
<h2 class="mb-6">Buchung bestätigen</h2>
|
||||||
|
|
||||||
{# Travel Summary #}
|
{# Travel Summary #}
|
||||||
@@ -51,67 +50,205 @@
|
|||||||
{% if loop.first %}<span class="text-sm text-gray-600">(Anmelder)</span>{% endif %}
|
{% if loop.first %}<span class="text-sm text-gray-600">(Anmelder)</span>{% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<dl class="grid grid-cols-2 gap-2 text-sm">
|
<dl class="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
{# Personal Data #}
|
||||||
{% if participant.dateOfBirth %}
|
{% if participant.dateOfBirth %}
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-600">Geburtsdatum</dt>
|
<dt class="text-gray-600 font-semibold">Geburtsdatum</dt>
|
||||||
<dd>{{ participant.dateOfBirth|date('d.m.Y') }}</dd>
|
<dd>{{ participant.dateOfBirth|date('d.m.Y') }}</dd>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if participant.gender %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-600 font-semibold">Geschlecht</dt>
|
||||||
|
<dd>{{ participant.gender == 'M' ? 'Männlich' : 'Weiblich' }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% if participant.email %}
|
{% if participant.email %}
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-600">E-Mail</dt>
|
<dt class="text-gray-600 font-semibold">E-Mail</dt>
|
||||||
<dd>{{ participant.email }}</dd>
|
<dd>{{ participant.email }}</dd>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if participant.mobile %}
|
{% if participant.mobile %}
|
||||||
<div>
|
<div>
|
||||||
<dt class="text-gray-600">Telefon</dt>
|
<dt class="text-gray-600 font-semibold">Telefon</dt>
|
||||||
<dd>{{ participant.mobile }}</dd>
|
<dd>{{ participant.mobile }}</dd>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if participant.nationality %}
|
||||||
{# Selected Services #}
|
<div>
|
||||||
{% set services = [] %}
|
<dt class="text-gray-600 font-semibold">Nationalität</dt>
|
||||||
{% if participant.board is not empty %}
|
<dd>{{ participant.nationality }}</dd>
|
||||||
{% for boardItem in participant.board %}
|
</div>
|
||||||
{% set services = services|merge(['Verpflegung: ' ~ boardItem.label]) %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if participant.skiPass %}
|
|
||||||
{% set services = services|merge(['Skipass: ' ~ participant.skiPass.label]) %}
|
|
||||||
{% endif %}
|
|
||||||
{% if participant.rentals is not empty %}
|
|
||||||
{% for rental in participant.rentals %}
|
|
||||||
{% set services = services|merge(['Ausrüstung: ' ~ rental.label]) %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if participant.courses is not empty %}
|
|
||||||
{% for course in participant.courses %}
|
|
||||||
{% set services = services|merge(['Kurs: ' ~ course.label]) %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if participant.additionalServices is not empty %}
|
|
||||||
{% for service in participant.additionalServices %}
|
|
||||||
{% set services = services|merge(['Service: ' ~ service.label]) %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if participant.insurance %}
|
|
||||||
{% set services = services|merge(['Versicherung: ' ~ participant.insurance.label]) %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if services is not empty %}
|
{# Address #}
|
||||||
|
{% if participant.address and (participant.address.street or participant.address.city) %}
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
<dt class="text-gray-600 mb-1">Gebuchte Leistungen</dt>
|
<dt class="text-gray-600 font-semibold">Adresse</dt>
|
||||||
|
<dd>
|
||||||
|
{% if participant.address.street %}{{ participant.address.street }}<br>{% endif %}
|
||||||
|
{% if participant.address.postCode or participant.address.city %}
|
||||||
|
{{ participant.address.postCode }} {{ participant.address.city }}<br>
|
||||||
|
{% endif %}
|
||||||
|
{% if participant.address.country %}{{ participant.address.country }}{% endif %}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Room Assignment #}
|
||||||
|
{% set assignedRoom = bookingCreateDto.travel.getRoomById(participant.assignedRoomId) %}
|
||||||
|
{% if assignedRoom %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-600 font-semibold">Zimmer</dt>
|
||||||
|
<dd>{{ assignedRoom.label }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Room Remarks #}
|
||||||
|
{% if participant.remarksRoom %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-600 font-semibold">Zimmerwunsch</dt>
|
||||||
|
<dd>{{ participant.remarksRoom }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Transportation Services #}
|
||||||
|
{% if participant.transportationOutbound %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-600 font-semibold">Anreise</dt>
|
||||||
|
<dd>{{ participant.transportationOutbound.label }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if participant.transportationInbound %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-600 font-semibold">Abreise</dt>
|
||||||
|
<dd>{{ participant.transportationInbound.label }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Pickup Locations #}
|
||||||
|
{% if participant.pickupOutbound %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-600 font-semibold">Zustieg Hinfahrt</dt>
|
||||||
|
<dd>{{ participant.pickupOutbound.label }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if participant.pickupInbound %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-600 font-semibold">Zustieg Rückfahrt</dt>
|
||||||
|
<dd>{{ participant.pickupInbound.label }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Parking & License Plate #}
|
||||||
|
{% if participant.parking %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-600 font-semibold">Parkplatz</dt>
|
||||||
|
<dd>Ja</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if participant.licensePlate %}
|
||||||
|
<div>
|
||||||
|
<dt class="text-gray-600 font-semibold">Kennzeichen</dt>
|
||||||
|
<dd>{{ participant.licensePlate }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Board Services #}
|
||||||
|
{% if participant.board is not empty %}
|
||||||
|
<div class="col-span-2">
|
||||||
|
<dt class="text-gray-600 font-semibold mb-1">Verpflegung</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<ul class="list-disc list-inside">
|
<ul class="list-disc list-inside">
|
||||||
{% for service in services %}
|
{% for boardItem in participant.board %}
|
||||||
<li>{{ service }}</li>
|
<li>{{ boardItem.label }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Ski Pass #}
|
||||||
|
{% if participant.skiPass %}
|
||||||
|
<div class="col-span-2">
|
||||||
|
<dt class="text-gray-600 font-semibold">Skipass</dt>
|
||||||
|
<dd>{{ participant.skiPass.label }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Rentals #}
|
||||||
|
{% if participant.rentals is not empty %}
|
||||||
|
<div class="col-span-2">
|
||||||
|
<dt class="text-gray-600 font-semibold mb-1">Ausrüstung</dt>
|
||||||
|
<dd>
|
||||||
|
<ul class="list-disc list-inside">
|
||||||
|
{% for rental in participant.rentals %}
|
||||||
|
<li>{{ rental.label }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Rental Insurance #}
|
||||||
|
{% if participant.rentalInsuranceSelected and participant.rentalInsurance %}
|
||||||
|
<div class="col-span-2">
|
||||||
|
<dt class="text-gray-600 font-semibold">Leihmaterial-Versicherung</dt>
|
||||||
|
<dd>{{ participant.rentalInsurance.label }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Body Dimensions (if rentals selected) #}
|
||||||
|
{% if participant.rentals is not empty %}
|
||||||
|
{% if participant.height or participant.weight or participant.shoeSize %}
|
||||||
|
<div class="col-span-2">
|
||||||
|
<dt class="text-gray-600 font-semibold mb-1">Körpermaße</dt>
|
||||||
|
<dd>
|
||||||
|
{% if participant.height %}Größe: {{ participant.height }} cm{% endif %}
|
||||||
|
{% if participant.weight %}{% if participant.height %}, {% endif %}Gewicht: {{ participant.weight }} kg{% endif %}
|
||||||
|
{% if participant.shoeSize %}{% if participant.height or participant.weight %}, {% endif %}Schuhgröße: {{ participant.shoeSize }}{% endif %}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Courses #}
|
||||||
|
{% if participant.courses is not empty %}
|
||||||
|
<div class="col-span-2">
|
||||||
|
<dt class="text-gray-600 font-semibold mb-1">Kurse</dt>
|
||||||
|
<dd>
|
||||||
|
<ul class="list-disc list-inside">
|
||||||
|
{% for course in participant.courses %}
|
||||||
|
<li>{{ course.label }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Additional Services #}
|
||||||
|
{% if participant.additionalServices is not empty %}
|
||||||
|
<div class="col-span-2">
|
||||||
|
<dt class="text-gray-600 font-semibold mb-1">Zusatzleistungen</dt>
|
||||||
|
<dd>
|
||||||
|
<ul class="list-disc list-inside">
|
||||||
|
{% for service in participant.additionalServices %}
|
||||||
|
<li>{{ service.label }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Insurance #}
|
||||||
|
{% if participant.insurance %}
|
||||||
|
<div class="col-span-2">
|
||||||
|
<dt class="text-gray-600 font-semibold">Reiseversicherung</dt>
|
||||||
|
<dd>{{ participant.insurance.label }}</dd>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -160,14 +297,4 @@
|
|||||||
|
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="booking-summary">
|
|
||||||
{% include 'booking/_summary.html.twig' with {
|
|
||||||
'bookingCreateDto': bookingCreateDto,
|
|
||||||
'participantCount': participantsCount,
|
|
||||||
'groupedSelectedRooms': groupedSelectedRooms,
|
|
||||||
'assignmentCounts': assignmentCounts
|
|
||||||
} %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user