From 34a8d42fc3fc10a8754ceee307c3a6d88ba98680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Fri, 31 Oct 2025 12:46:37 +0100 Subject: [PATCH] fix: keep browser history intact when navigating between cards view and participant form --- .../Booking/Create/Step2Controller.php | 40 ++++++++------ .../Booking/Edit/IndexController.php | 52 +++++++++++-------- src/Htmx/HxTrait.php | 12 ++++- templates/booking/_participant_card.html.twig | 22 +++----- templates/booking/_participant_form.html.twig | 28 +++++----- .../create/step_2_participant.html.twig | 26 ++++++++++ templates/booking/edit/participant.html.twig | 26 ++++++++++ 7 files changed, 136 insertions(+), 70 deletions(-) create mode 100644 templates/booking/create/step_2_participant.html.twig create mode 100644 templates/booking/edit/participant.html.twig diff --git a/src/Controller/Booking/Create/Step2Controller.php b/src/Controller/Booking/Create/Step2Controller.php index 45dd677..f56127c 100644 --- a/src/Controller/Booking/Create/Step2Controller.php +++ b/src/Controller/Booking/Create/Step2Controller.php @@ -120,7 +120,8 @@ class Step2Controller extends AbstractController return $this->htmxOobResponse( 'booking/create/step_2.html.twig', ['participant_cards', 'booking_summary'], - $templateData + $templateData, + $this->generateUrl('app_booking_create_step_2') ); } @@ -169,21 +170,28 @@ class Step2Controller extends AbstractController // Get detailed pricing data for summary sidebar $summary = $this->bookingService->getRoomSummaryAndParticipantCount($bookingDto); - // Render form and sidebar with OOB swap using htmxOobResponse - // This ensures both initial load and refresh use the same block-based rendering - return $this->htmxOobResponse( - 'booking/_participant_form.html.twig', - ['participant_form', 'booking_summary'], - [ - 'form' => $form->createView(), - 'participantIndex' => $index, - 'bookingDto' => $bookingDto, - 'summaryData' => $summaryData, - 'pricingData' => $summary['pricing'], - 'refreshRouteName' => 'app_booking_create_step_2_participant_refresh', - 'submitRouteName' => 'app_booking_create_step_2_participant', - ] - ); + $templateData = [ + 'form' => $form->createView(), + 'participantIndex' => $index, + 'bookingDto' => $bookingDto, + 'summaryData' => $summaryData, + 'pricingData' => $summary['pricing'], + 'refreshRouteName' => 'app_booking_create_step_2_participant_refresh', + 'submitRouteName' => 'app_booking_create_step_2_participant', + ]; + + // HTMX request: render blocks only with OOB swap + if ($this->isHxRequest($request)) { + return $this->htmxOobResponse( + 'booking/_participant_form.html.twig', + ['participant_form', 'booking_summary'], + $templateData, + $this->generateUrl('app_booking_create_step_2_participant', ['index' => $index]) + ); + } + + // Regular request: render full template + return $this->render('booking/create/step_2_participant.html.twig', $templateData); } /** diff --git a/src/Controller/Booking/Edit/IndexController.php b/src/Controller/Booking/Edit/IndexController.php index 8023469..d317f4e 100644 --- a/src/Controller/Booking/Edit/IndexController.php +++ b/src/Controller/Booking/Edit/IndexController.php @@ -188,7 +188,8 @@ class IndexController extends AbstractController return $this->htmxOobResponse( 'booking/edit/index.html.twig', ['participant_cards', 'booking_summary'], - $templateData + $templateData, + $this->generateUrl('app_booking_edit', ['id' => $id]) ); } @@ -274,27 +275,34 @@ class IndexController extends AbstractController // Fetch mutable data for form constraints $mutableData = $this->travelDataService->getMutabilityData($bookingData->dateId); - // Render form and sidebar with OOB swap using htmxOobResponse - // This ensures both initial load and refresh use the same block-based rendering - return $this->htmxOobResponse( - 'booking/_participant_form.html.twig', - ['participant_form', 'booking_summary'], - [ - 'form' => $form->createView(), - 'participantIndex' => $index, - 'bookingDto' => $bookingDto, - 'bookingData' => $bookingData, - 'mutableData' => $mutableData, - 'summaryData' => $summaryData, - 'pricingData' => $summary['pricing'], - 'refreshRouteName' => 'app_booking_edit_participant_refresh', - 'refreshRouteParams' => ['id' => $id, 'index' => $index], - 'submitRouteName' => 'app_booking_edit_participant', - 'submitRouteParams' => ['id' => $id, 'index' => $index], - 'cancelRouteName' => 'app_booking_edit', - 'cancelRouteParams' => ['id' => $id], - ] - ); + $templateData = [ + 'form' => $form->createView(), + 'participantIndex' => $index, + 'bookingDto' => $bookingDto, + 'bookingData' => $bookingData, + 'mutableData' => $mutableData, + 'summaryData' => $summaryData, + 'pricingData' => $summary['pricing'], + 'refreshRouteName' => 'app_booking_edit_participant_refresh', + 'refreshRouteParams' => ['id' => $id, 'index' => $index], + 'submitRouteName' => 'app_booking_edit_participant', + 'submitRouteParams' => ['id' => $id, 'index' => $index], + 'cancelRouteName' => 'app_booking_edit', + 'cancelRouteParams' => ['id' => $id], + ]; + + // HTMX request: render blocks only with OOB swap + if ($this->isHxRequest($request)) { + return $this->htmxOobResponse( + 'booking/_participant_form.html.twig', + ['participant_form', 'booking_summary'], + $templateData, + $this->generateUrl('app_booking_edit_participant', ['id' => $id, 'index' => $index]) + ); + } + + // Regular request: render full template + return $this->render('booking/edit/participant.html.twig', $templateData); } /** diff --git a/src/Htmx/HxTrait.php b/src/Htmx/HxTrait.php index 2b9bb21..419145d 100644 --- a/src/Htmx/HxTrait.php +++ b/src/Htmx/HxTrait.php @@ -121,10 +121,11 @@ trait HxTrait * @param string $templateName The name of the Twig template * @param string[] $blockNames An array of block names to render * @param array $context The context to pass to the template + * @param string|null $pushUrl Optional URL to push to browser history * * @return Response Response containing all rendered blocks with OOB context */ - protected function htmxOobResponse(string $templateName, array $blockNames, array $context = []): Response + protected function htmxOobResponse(string $templateName, array $blockNames, array $context = [], ?string $pushUrl = null): Response { $html = ''; // Add a flag to the context so templates can conditionally add the hx-swap-oob attribute. @@ -135,6 +136,13 @@ trait HxTrait $html .= $this->renderBlockView($templateName, $blockName, $oobContext); } - return new Response($html); + $response = new Response($html); + + // Set HX-Push-Url header if URL is provided + if (null !== $pushUrl) { + $response->headers->set('HX-Push-Url', $pushUrl); + } + + return $response; } } diff --git a/templates/booking/_participant_card.html.twig b/templates/booking/_participant_card.html.twig index 7735369..9736073 100644 --- a/templates/booking/_participant_card.html.twig +++ b/templates/booking/_participant_card.html.twig @@ -50,23 +50,17 @@ {% else %} {% if mode == 'edit' %} - + {% else %} - + {% endif %} {% endif %} diff --git a/templates/booking/_participant_form.html.twig b/templates/booking/_participant_form.html.twig index e4a33a7..7b62297 100644 --- a/templates/booking/_participant_form.html.twig +++ b/templates/booking/_participant_form.html.twig @@ -360,23 +360,17 @@
{% if cancelRouteName is defined %} - + {% else %} - + {% endif %}
{% endblock %} -{# Sidebar summary with conditional OOB swap #} +{# Sidebar summary with conditional OOB swap - only render for HTMX requests #} +{% if htmx_oob_swap|default(false) %} {% block booking_summary %} -
+
{% include 'booking/_summary.html.twig' with { 'bookingCreateDto': bookingDto, 'participantCount': summaryData.participantsCount, @@ -400,3 +395,4 @@ } %}
{% endblock %} +{% endif %} diff --git a/templates/booking/create/step_2_participant.html.twig b/templates/booking/create/step_2_participant.html.twig new file mode 100644 index 0000000..27b2b27 --- /dev/null +++ b/templates/booking/create/step_2_participant.html.twig @@ -0,0 +1,26 @@ +{% extends 'layout.html.twig' %} + +{% block content %} + {% include '_partials/_flashes.html.twig' %} +

Neue Buchung

+ +
+ {# Main content area - participant form #} +
+ {% include 'booking/_participant_form.html.twig' %} +
+ + {# Sidebar summary #} +
+ {% include 'booking/_summary.html.twig' with { + 'bookingCreateDto': bookingDto, + 'participantCount': summaryData.participantsCount, + 'groupedSelectedRooms': summaryData.groupedSelectedRooms, + 'assignmentCounts': summaryData.assignmentCounts, + 'pricingData': pricingData + } %} +
+
+ + {% include 'booking/_cancel_link.html.twig' %} +{% endblock %} diff --git a/templates/booking/edit/participant.html.twig b/templates/booking/edit/participant.html.twig new file mode 100644 index 0000000..044141a --- /dev/null +++ b/templates/booking/edit/participant.html.twig @@ -0,0 +1,26 @@ +{% extends 'layout.html.twig' %} + +{% block content %} + {% include '_partials/_flashes.html.twig' %} +

Buchung bearbeiten

+ +
+ {# Main content area - participant form #} +
+ {% include 'booking/_participant_form.html.twig' %} +
+ + {# Sidebar summary #} +
+ {% include 'booking/_summary.html.twig' with { + 'bookingCreateDto': bookingDto, + 'participantCount': summaryData.participantsCount, + 'groupedSelectedRooms': summaryData.groupedSelectedRooms, + 'assignmentCounts': summaryData.assignmentCounts, + 'pricingData': pricingData + } %} +
+
+ + {% include 'booking/_cancel_link.html.twig' %} +{% endblock %}