From 65a33922cfd772ee1a3df7a6630a6297067ac384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Thu, 24 Jul 2025 12:29:42 +0200 Subject: [PATCH] wip: improved booking summary --- .claude/settings.local.json | 5 +- src/BusProNet/Model/Room.php | 16 +++++ src/BusProNet/Model/Travel.php | 12 ++-- .../Booking/CreateStep1Controller.php | 15 ++++- .../Booking/CreateStep2Controller.php | 6 ++ src/Form/Model/RoomSelectionDto.php | 9 --- src/Service/BookingService.php | 47 +++++++++++++++ templates/booking/_room_summary.html.twig | 15 ----- templates/booking/_summary.html.twig | 32 ++++++++++ templates/booking/create_step_1.html.twig | 43 +++++++++++--- templates/booking/create_step_2.html.twig | 58 ++++--------------- 11 files changed, 168 insertions(+), 90 deletions(-) delete mode 100644 templates/booking/_room_summary.html.twig create mode 100644 templates/booking/_summary.html.twig diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ac48044..83e02fd 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,9 +1,8 @@ { "permissions": { "allow": [ - "Bash(php -l:*)", - "Bash(php:*)" + "Bash(php -l:*)" ], "deny": [] } -} \ No newline at end of file +} diff --git a/src/BusProNet/Model/Room.php b/src/BusProNet/Model/Room.php index 24e9ad3..219f1c3 100644 --- a/src/BusProNet/Model/Room.php +++ b/src/BusProNet/Model/Room.php @@ -17,6 +17,9 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; */ class Room { + public const SELECTION_TYPE_BY_PAX = 'by_pax'; + public const SELECTION_TYPE_BY_ROOM = 'by_room'; + #[Groups(['api:list', 'api:single'])] public ?int $id = null; @@ -72,4 +75,17 @@ class Room #[Groups(['booking'])] 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; + } } diff --git a/src/BusProNet/Model/Travel.php b/src/BusProNet/Model/Travel.php index 8c759f8..88bd49d 100644 --- a/src/BusProNet/Model/Travel.php +++ b/src/BusProNet/Model/Travel.php @@ -188,12 +188,16 @@ class Travel * greater than zero and have an available status. This ensures only * bookable rooms are returned for selection. * - * @return array The filtered array of available rooms + * @return array The filtered array of available rooms, indexed by room ID */ public function getAvailableRooms(): array { - return array_filter($this->rooms, function (Room $room) { - return $room->available > 0 && Constants::STATUS_AVAILABLE === $room->status; - }); + $result = []; + foreach ($this->rooms as $room) { + if ($room->available > 0 && Constants::STATUS_AVAILABLE === $room->status) { + $result[$room->id] = $room; + } + } + return $result; } } diff --git a/src/Controller/Booking/CreateStep1Controller.php b/src/Controller/Booking/CreateStep1Controller.php index 9f3ca9c..8c5f73c 100644 --- a/src/Controller/Booking/CreateStep1Controller.php +++ b/src/Controller/Booking/CreateStep1Controller.php @@ -23,7 +23,8 @@ class CreateStep1Controller extends AbstractController public function __construct( private readonly BookingService $bookingService, - ) { + ) + { } /** @@ -51,11 +52,17 @@ class CreateStep1Controller extends AbstractController 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', [ 'bookingCreateDto' => $bookingCreateDto, 'roomSummary' => $summary['selectedRooms'], 'participantCount' => $summary['participantCount'], 'form' => $form->createView(), + 'groupedRooms' => $groupedRooms, + 'groupedSelectedRooms' => $groupedSelectedRooms, ]); } @@ -76,10 +83,14 @@ class CreateStep1Controller extends AbstractController $this->bookingService->saveBookingCreateDto($request, $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'], 'participantCount' => $summary['participantCount'], + 'groupedSelectedRooms' => $groupedSelectedRooms, ]); } } diff --git a/src/Controller/Booking/CreateStep2Controller.php b/src/Controller/Booking/CreateStep2Controller.php index 18b9b47..c63cccd 100644 --- a/src/Controller/Booking/CreateStep2Controller.php +++ b/src/Controller/Booking/CreateStep2Controller.php @@ -64,12 +64,15 @@ class CreateStep2Controller extends AbstractController } $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', [ 'bookingCreateDto' => $bookingCreateDto, 'participantsCount' => $participantsCount, 'assignmentCounts' => $roomAssignmentCounts, 'form' => $form->createView(), + 'groupedSelectedRooms' => $groupedSelectedRooms, ]); } @@ -93,6 +96,8 @@ class CreateStep2Controller extends AbstractController $this->bookingService->saveBookingCreateDto($request, $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. // We can now render the blocks with the fresh data. @@ -104,6 +109,7 @@ class CreateStep2Controller extends AbstractController 'bookingCreateDto' => $bookingCreateDto, 'participantsCount' => $participantsCount, 'assignmentCounts' => $roomAssignmentCounts, + 'groupedSelectedRooms' => $groupedSelectedRooms, ] ); } diff --git a/src/Form/Model/RoomSelectionDto.php b/src/Form/Model/RoomSelectionDto.php index 0992907..ccd5af2 100644 --- a/src/Form/Model/RoomSelectionDto.php +++ b/src/Form/Model/RoomSelectionDto.php @@ -16,13 +16,4 @@ class RoomSelectionDto public int $maxQuantity = 100; public int $minPax = 0; - - public function getType(): string - { - if (1 === preg_match('/bett/i', $this->roomLabel)) { - return 'by_pax'; - } - - return 'by_room'; - } } diff --git a/src/Service/BookingService.php b/src/Service/BookingService.php index 21db560..847aab9 100644 --- a/src/Service/BookingService.php +++ b/src/Service/BookingService.php @@ -137,4 +137,51 @@ class BookingService 'participantCount' => $participantCount, ]; } + + /** + * Groups available rooms by selection type ('by_pax' or 'by_room'). + * + * @param array $rooms Rooms indexed by room ID + * @return array{by_pax: array, by_room: array} + */ + 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 $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; + } } diff --git a/templates/booking/_room_summary.html.twig b/templates/booking/_room_summary.html.twig deleted file mode 100644 index aeab0bc..0000000 --- a/templates/booking/_room_summary.html.twig +++ /dev/null @@ -1,15 +0,0 @@ -
-

Zusammenfassung

-

Zimmer

-
    - {% for roomSelection in roomSummary %} -
  • - {{ roomSelection.quantity }} x {{ roomSelection.roomLabel }} -
  • - {% endfor %} -
-

Anzahl Teilnehmer

-

- {{ participantCount }} -

-
\ No newline at end of file diff --git a/templates/booking/_summary.html.twig b/templates/booking/_summary.html.twig new file mode 100644 index 0000000..bd767b4 --- /dev/null +++ b/templates/booking/_summary.html.twig @@ -0,0 +1,32 @@ +

Zusammenfassung

+

Reise: {{ bookingCreateDto.travel.label }}

+

Datum: {{ bookingCreateDto.travel.dateFrom|date('d.m.Y') }} - {{ bookingCreateDto.travel.dateTo|date('d.m.Y') }}

+

Hotel: {{ bookingCreateDto.travel.hotel.name }}

+{% if groupedSelectedRooms.by_room is not empty %} +

Zimmer

+
    + {% for roomSelection in groupedSelectedRooms.by_room %} +
  • + {{ roomSelection.quantity }} x {{ roomSelection.roomLabel }} + {% if assignmentCounts is defined and assignmentCounts[roomSelection.roomId] is defined %} + {{ assignmentCounts[roomSelection.roomId] }}/{{ roomSelection.quantity }} belegt + {% endif %} +
  • + {% endfor %} +
+{% endif %} +{% if groupedSelectedRooms.by_pax is not empty %} +

Betten

+
    + {% for roomSelection in groupedSelectedRooms.by_pax %} +
  • + {{ roomSelection.quantity }} x {{ roomSelection.roomLabel }} + {% if assignmentCounts is defined and assignmentCounts[roomSelection.roomId] is defined %} + {{ assignmentCounts[roomSelection.roomId] }}/{{ roomSelection.quantity }} belegt + {% endif %} +
  • + {% endfor %} +
+{% endif %} +

Anzahl Teilnehmer

+

{{ participantCount }}

diff --git a/templates/booking/create_step_1.html.twig b/templates/booking/create_step_1.html.twig index 5da51f9..deedf8e 100644 --- a/templates/booking/create_step_1.html.twig +++ b/templates/booking/create_step_1.html.twig @@ -5,28 +5,53 @@

Neue Buchung

-

Zimmerauswahl

+

+ Unterkunft +

{{ form_start(form) }} -
- {% for roomSelection in form.roomSelections %} - {{ form_row(roomSelection, { + {% if groupedRooms.by_room is not empty %} +

+ Zimmer +

+ {% for roomId, room in groupedRooms.by_room %} + {{ form_row(form.roomSelections[roomId], { 'attr': { 'hx-post': path('app_booking_create_step_1_room_summary'), 'hx-target': '#booking-summary', - 'hx-swap': 'outerHTML', + 'hx-swap': 'innerHTML', 'hx-trigger': 'change' } }) }} {% endfor %} -
-
+ {% endif %} + {% if groupedRooms.by_pax is not empty %} +

+ Betten +

+ {% 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 %} +
{{ form_rest(form) }} {{ form_end(form) }}
-
- {% include 'booking/_room_summary.html.twig' %} +
+ {% include 'booking/_summary.html.twig' with { + 'bookingCreateDto': bookingCreateDto, + 'participantCount': participantCount, + 'groupedSelectedRooms': groupedSelectedRooms, + 'assignmentCounts': [] + } %}
{% endblock %} diff --git a/templates/booking/create_step_2.html.twig b/templates/booking/create_step_2.html.twig index 47e90f7..1dc7844 100644 --- a/templates/booking/create_step_2.html.twig +++ b/templates/booking/create_step_2.html.twig @@ -62,53 +62,15 @@ {{ form_rest(form) }} {{ form_end(form) }}
-
- {# This block contains the booking summary #} - {% block booking_summary %} -
-

Zusammenfassung

-

- Reise -

-

- {{ bookingCreateDto.travel.label }} -

-

- Datum -

-

- {{ bookingCreateDto.travel.dateFrom | date('d.m.Y') }} - {{ bookingCreateDto.travel.dateTo | date('d.m.Y') }} -

-

- Unterkunft -

-

- {{ bookingCreateDto.travel.hotel.name }} -

- {% if participantsCount > 0 %} -

- Anzahl Teilnehmer -

-

- {{ participantsCount }} -

- {% endif %} -

- Zimmer -

-
    - {% for roomSelection in bookingCreateDto.selectedRooms %} - {% set assignedCount = assignmentCounts[roomSelection.roomId] ?? 0 %} -
  • - {{ roomSelection.roomLabel }} - - ({{ assignedCount }}/{{ roomSelection.quantity }}) - -
  • - {% endfor %} -
-
- {% endblock %} -
+ {% block booking_summary %} +
+ {% include 'booking/_summary.html.twig' with { + 'bookingCreateDto': bookingCreateDto, + 'participantCount': participantsCount, + 'groupedSelectedRooms': groupedSelectedRooms, + 'assignmentCounts': assignmentCounts + } %} +
+ {% endblock %}
{% endblock %}