wip: improved booking summary
This commit is contained in:
@@ -1,9 +1,8 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(php -l:*)",
|
"Bash(php -l:*)"
|
||||||
"Bash(php:*)"
|
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
*/
|
*/
|
||||||
class Room
|
class Room
|
||||||
{
|
{
|
||||||
|
public const SELECTION_TYPE_BY_PAX = 'by_pax';
|
||||||
|
public const SELECTION_TYPE_BY_ROOM = 'by_room';
|
||||||
|
|
||||||
#[Groups(['api:list', 'api:single'])]
|
#[Groups(['api:list', 'api:single'])]
|
||||||
public ?int $id = null;
|
public ?int $id = null;
|
||||||
|
|
||||||
@@ -72,4 +75,17 @@ class Room
|
|||||||
|
|
||||||
#[Groups(['booking'])]
|
#[Groups(['booking'])]
|
||||||
public array $individualPrice = [];
|
public array $individualPrice = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the selection type of the room based on its label.
|
||||||
|
*
|
||||||
|
* @return string Either self::SELECTION_TYPE_BY_PAX or self::SELECTION_TYPE_BY_ROOM
|
||||||
|
*/
|
||||||
|
public function getSelectionType(): string
|
||||||
|
{
|
||||||
|
if (1 === preg_match('/bett/i', $this->label)) {
|
||||||
|
return self::SELECTION_TYPE_BY_PAX;
|
||||||
|
}
|
||||||
|
return self::SELECTION_TYPE_BY_ROOM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,12 +188,16 @@ class Travel
|
|||||||
* greater than zero and have an available status. This ensures only
|
* greater than zero and have an available status. This ensures only
|
||||||
* bookable rooms are returned for selection.
|
* bookable rooms are returned for selection.
|
||||||
*
|
*
|
||||||
* @return array<Room> The filtered array of available rooms
|
* @return array<int, Room> The filtered array of available rooms, indexed by room ID
|
||||||
*/
|
*/
|
||||||
public function getAvailableRooms(): array
|
public function getAvailableRooms(): array
|
||||||
{
|
{
|
||||||
return array_filter($this->rooms, function (Room $room) {
|
$result = [];
|
||||||
return $room->available > 0 && Constants::STATUS_AVAILABLE === $room->status;
|
foreach ($this->rooms as $room) {
|
||||||
});
|
if ($room->available > 0 && Constants::STATUS_AVAILABLE === $room->status) {
|
||||||
|
$result[$room->id] = $room;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class CreateStep1Controller extends AbstractController
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly BookingService $bookingService,
|
private readonly BookingService $bookingService,
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,11 +52,17 @@ class CreateStep1Controller extends AbstractController
|
|||||||
return $this->redirectToRoute('app_booking_create_step_2');
|
return $this->redirectToRoute('app_booking_create_step_2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||||
|
$groupedRooms = $this->bookingService->groupRoomsBySelectionType($availableRooms);
|
||||||
|
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($summary['selectedRooms'], $availableRooms);
|
||||||
|
|
||||||
return $this->render('booking/create_step_1.html.twig', [
|
return $this->render('booking/create_step_1.html.twig', [
|
||||||
'bookingCreateDto' => $bookingCreateDto,
|
'bookingCreateDto' => $bookingCreateDto,
|
||||||
'roomSummary' => $summary['selectedRooms'],
|
'roomSummary' => $summary['selectedRooms'],
|
||||||
'participantCount' => $summary['participantCount'],
|
'participantCount' => $summary['participantCount'],
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
|
'groupedRooms' => $groupedRooms,
|
||||||
|
'groupedSelectedRooms' => $groupedSelectedRooms,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,10 +83,14 @@ class CreateStep1Controller extends AbstractController
|
|||||||
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
||||||
|
|
||||||
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
$summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingCreateDto);
|
||||||
|
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||||
|
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($summary['selectedRooms'], $availableRooms);
|
||||||
|
|
||||||
return $this->render('booking/_room_summary.html.twig', [
|
return $this->render('booking/_summary.html.twig', [
|
||||||
|
'bookingCreateDto' => $bookingCreateDto,
|
||||||
'roomSummary' => $summary['selectedRooms'],
|
'roomSummary' => $summary['selectedRooms'],
|
||||||
'participantCount' => $summary['participantCount'],
|
'participantCount' => $summary['participantCount'],
|
||||||
|
'groupedSelectedRooms' => $groupedSelectedRooms,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,12 +64,15 @@ class CreateStep2Controller extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
|
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
|
||||||
|
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||||
|
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms);
|
||||||
|
|
||||||
return $this->render('booking/create_step_2.html.twig', [
|
return $this->render('booking/create_step_2.html.twig', [
|
||||||
'bookingCreateDto' => $bookingCreateDto,
|
'bookingCreateDto' => $bookingCreateDto,
|
||||||
'participantsCount' => $participantsCount,
|
'participantsCount' => $participantsCount,
|
||||||
'assignmentCounts' => $roomAssignmentCounts,
|
'assignmentCounts' => $roomAssignmentCounts,
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
|
'groupedSelectedRooms' => $groupedSelectedRooms,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +96,8 @@ class CreateStep2Controller extends AbstractController
|
|||||||
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
$this->bookingService->saveBookingCreateDto($request, $bookingCreateDto);
|
||||||
|
|
||||||
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
|
$roomAssignmentCounts = $this->bookingService->getRoomAssignmentCounts($bookingCreateDto);
|
||||||
|
$availableRooms = $bookingCreateDto->travel->getAvailableRooms();
|
||||||
|
$groupedSelectedRooms = $this->bookingService->groupRoomSelectionsByType($bookingCreateDto->getSelectedRooms(), $availableRooms);
|
||||||
|
|
||||||
// The DTO is now updated with the latest selection.
|
// The DTO is now updated with the latest selection.
|
||||||
// We can now render the blocks with the fresh data.
|
// We can now render the blocks with the fresh data.
|
||||||
@@ -104,6 +109,7 @@ class CreateStep2Controller extends AbstractController
|
|||||||
'bookingCreateDto' => $bookingCreateDto,
|
'bookingCreateDto' => $bookingCreateDto,
|
||||||
'participantsCount' => $participantsCount,
|
'participantsCount' => $participantsCount,
|
||||||
'assignmentCounts' => $roomAssignmentCounts,
|
'assignmentCounts' => $roomAssignmentCounts,
|
||||||
|
'groupedSelectedRooms' => $groupedSelectedRooms,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,4 @@ class RoomSelectionDto
|
|||||||
public int $maxQuantity = 100;
|
public int $maxQuantity = 100;
|
||||||
|
|
||||||
public int $minPax = 0;
|
public int $minPax = 0;
|
||||||
|
|
||||||
public function getType(): string
|
|
||||||
{
|
|
||||||
if (1 === preg_match('/bett/i', $this->roomLabel)) {
|
|
||||||
return 'by_pax';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'by_room';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,4 +137,51 @@ class BookingService
|
|||||||
'participantCount' => $participantCount,
|
'participantCount' => $participantCount,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups available rooms by selection type ('by_pax' or 'by_room').
|
||||||
|
*
|
||||||
|
* @param array<int, Room> $rooms Rooms indexed by room ID
|
||||||
|
* @return array{by_pax: array<int, Room>, by_room: array<int, Room>}
|
||||||
|
*/
|
||||||
|
public function groupRoomsBySelectionType(array $rooms): array
|
||||||
|
{
|
||||||
|
$groups = [
|
||||||
|
'by_pax' => [],
|
||||||
|
'by_room' => [],
|
||||||
|
];
|
||||||
|
foreach ($rooms as $room) {
|
||||||
|
if (stripos($room->label, 'bett') !== false) {
|
||||||
|
$groups['by_pax'][$room->id] = $room;
|
||||||
|
} else {
|
||||||
|
$groups['by_room'][$room->id] = $room;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups roomSelections by selection type ('by_pax' or 'by_room'), using Room::getSelectionType().
|
||||||
|
*
|
||||||
|
* @param array $roomSelections Array of selected RoomSelectionDto
|
||||||
|
* @param array<int, Room> $roomsById Rooms indexed by room ID
|
||||||
|
* @return array{by_pax: array, by_room: array}
|
||||||
|
*/
|
||||||
|
public function groupRoomSelectionsByType(array $roomSelections, array $roomsById): array
|
||||||
|
{
|
||||||
|
$groups = [
|
||||||
|
'by_pax' => [],
|
||||||
|
'by_room' => [],
|
||||||
|
];
|
||||||
|
foreach ($roomSelections as $roomSelection) {
|
||||||
|
$room = $roomsById[$roomSelection->roomId] ?? null;
|
||||||
|
if ($room) {
|
||||||
|
$type = $room->getSelectionType();
|
||||||
|
$groups[$type][] = $roomSelection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groups;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<div id="booking-summary">
|
|
||||||
<h3>Zusammenfassung</h3>
|
|
||||||
<h4>Zimmer</h4>
|
|
||||||
<ul class="list-disc list-inside pb-4">
|
|
||||||
{% for roomSelection in roomSummary %}
|
|
||||||
<li>
|
|
||||||
{{ roomSelection.quantity }} x {{ roomSelection.roomLabel }}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<h4>Anzahl Teilnehmer</h4>
|
|
||||||
<p>
|
|
||||||
{{ participantCount }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<h3>Zusammenfassung</h3>
|
||||||
|
<p>Reise: {{ bookingCreateDto.travel.label }}</p>
|
||||||
|
<p>Datum: {{ bookingCreateDto.travel.dateFrom|date('d.m.Y') }} - {{ bookingCreateDto.travel.dateTo|date('d.m.Y') }}</p>
|
||||||
|
<p>Hotel: {{ bookingCreateDto.travel.hotel.name }}</p>
|
||||||
|
{% if groupedSelectedRooms.by_room is not empty %}
|
||||||
|
<h4>Zimmer</h4>
|
||||||
|
<ul class="list-disc pl-4 pb-4">
|
||||||
|
{% for roomSelection in groupedSelectedRooms.by_room %}
|
||||||
|
<li>
|
||||||
|
{{ roomSelection.quantity }} x {{ roomSelection.roomLabel }}
|
||||||
|
{% if assignmentCounts is defined and assignmentCounts[roomSelection.roomId] is defined %}
|
||||||
|
<span class="block text-sm">{{ assignmentCounts[roomSelection.roomId] }}/{{ roomSelection.quantity }} belegt</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% if groupedSelectedRooms.by_pax is not empty %}
|
||||||
|
<h4>Betten</h4>
|
||||||
|
<ul class="list-disc pl-4 pb-4">
|
||||||
|
{% for roomSelection in groupedSelectedRooms.by_pax %}
|
||||||
|
<li>
|
||||||
|
{{ roomSelection.quantity }} x {{ roomSelection.roomLabel }}
|
||||||
|
{% if assignmentCounts is defined and assignmentCounts[roomSelection.roomId] is defined %}
|
||||||
|
<span class="block text-sm">{{ assignmentCounts[roomSelection.roomId] }}/{{ roomSelection.quantity }} belegt</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
<h4>Anzahl Teilnehmer</h4>
|
||||||
|
<p>{{ participantCount }}</p>
|
||||||
@@ -5,28 +5,53 @@
|
|||||||
<h1>Neue Buchung</h1>
|
<h1>Neue Buchung</h1>
|
||||||
<div class="grid grid-cols-3 gap-8">
|
<div class="grid grid-cols-3 gap-8">
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
<h2>Zimmerauswahl</h2>
|
<h2 class="pb-4">
|
||||||
|
Unterkunft
|
||||||
|
</h2>
|
||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
<div>
|
{% if groupedRooms.by_room is not empty %}
|
||||||
{% for roomSelection in form.roomSelections %}
|
<h3>
|
||||||
{{ form_row(roomSelection, {
|
Zimmer
|
||||||
|
</h3>
|
||||||
|
{% for roomId, room in groupedRooms.by_room %}
|
||||||
|
{{ form_row(form.roomSelections[roomId], {
|
||||||
'attr': {
|
'attr': {
|
||||||
'hx-post': path('app_booking_create_step_1_room_summary'),
|
'hx-post': path('app_booking_create_step_1_room_summary'),
|
||||||
'hx-target': '#booking-summary',
|
'hx-target': '#booking-summary',
|
||||||
'hx-swap': 'outerHTML',
|
'hx-swap': 'innerHTML',
|
||||||
'hx-trigger': 'change'
|
'hx-trigger': 'change'
|
||||||
}
|
}
|
||||||
}) }}
|
}) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="flex justify-end">
|
{% if groupedRooms.by_pax is not empty %}
|
||||||
|
<h3>
|
||||||
|
Betten
|
||||||
|
</h3>
|
||||||
|
{% for roomId, room in groupedRooms.by_pax %}
|
||||||
|
{{ form_row(form.roomSelections[roomId], {
|
||||||
|
'attr': {
|
||||||
|
'hx-post': path('app_booking_create_step_1_room_summary'),
|
||||||
|
'hx-target': '#booking-summary',
|
||||||
|
'hx-swap': 'innerHTML',
|
||||||
|
'hx-trigger': 'change'
|
||||||
|
}
|
||||||
|
}) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="flex justify-end pt-4">
|
||||||
<button type="submit" class="button bg-button bg-button--secondary">Weiter</button>
|
<button type="submit" class="button bg-button bg-button--secondary">Weiter</button>
|
||||||
</div>
|
</div>
|
||||||
{{ form_rest(form) }}
|
{{ form_rest(form) }}
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div id="booking-summary">
|
||||||
{% include 'booking/_room_summary.html.twig' %}
|
{% include 'booking/_summary.html.twig' with {
|
||||||
|
'bookingCreateDto': bookingCreateDto,
|
||||||
|
'participantCount': participantCount,
|
||||||
|
'groupedSelectedRooms': groupedSelectedRooms,
|
||||||
|
'assignmentCounts': []
|
||||||
|
} %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -62,53 +62,15 @@
|
|||||||
{{ form_rest(form) }}
|
{{ form_rest(form) }}
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{% block booking_summary %}
|
||||||
{# This block contains the booking summary #}
|
<div id="booking-summary"{% if htmx_oob_swap is defined and htmx_oob_swap %} hx-swap-oob="true"{% endif %}>
|
||||||
{% block booking_summary %}
|
{% include 'booking/_summary.html.twig' with {
|
||||||
<div id="booking-summary"{% if htmx_oob_swap is defined and htmx_oob_swap %} hx-swap-oob="true"{% endif %}>
|
'bookingCreateDto': bookingCreateDto,
|
||||||
<h3>Zusammenfassung</h3>
|
'participantCount': participantsCount,
|
||||||
<h4>
|
'groupedSelectedRooms': groupedSelectedRooms,
|
||||||
Reise
|
'assignmentCounts': assignmentCounts
|
||||||
</h4>
|
} %}
|
||||||
<p>
|
</div>
|
||||||
{{ bookingCreateDto.travel.label }}
|
{% endblock %}
|
||||||
</p>
|
|
||||||
<h4>
|
|
||||||
Datum
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{{ bookingCreateDto.travel.dateFrom | date('d.m.Y') }} - {{ bookingCreateDto.travel.dateTo | date('d.m.Y') }}
|
|
||||||
</p>
|
|
||||||
<h4>
|
|
||||||
Unterkunft
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{{ bookingCreateDto.travel.hotel.name }}
|
|
||||||
</p>
|
|
||||||
{% if participantsCount > 0 %}
|
|
||||||
<h4>
|
|
||||||
Anzahl Teilnehmer
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{{ participantsCount }}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
<h4>
|
|
||||||
Zimmer
|
|
||||||
</h4>
|
|
||||||
<ul>
|
|
||||||
{% for roomSelection in bookingCreateDto.selectedRooms %}
|
|
||||||
{% set assignedCount = assignmentCounts[roomSelection.roomId] ?? 0 %}
|
|
||||||
<li>
|
|
||||||
{{ roomSelection.roomLabel }}
|
|
||||||
<span class="text-sm pl-2 {% if assignedCount == roomSelection.quantity %}text-green-600{% else %}text-gray-500{% endif %}">
|
|
||||||
({{ assignedCount }}/{{ roomSelection.quantity }})
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user