feat: add subtypes PAR and LVS as bookable additional services
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
import { Controller } from '@hotwired/stimulus'
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
|
||||||
|
static classes = [ 'hidden' ]
|
||||||
|
static targets = [ 'container', 'checkboxGroup' ]
|
||||||
|
static values = { visible: Boolean }
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.updateVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
this.updateVisibility()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateVisibility() {
|
||||||
|
if (!this.hasCheckboxGroupTarget) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkboxes = this.checkboxGroupTarget.querySelectorAll('input[type="checkbox"]')
|
||||||
|
const hasChecked = Array.from(checkboxes).some(checkbox => checkbox.checked)
|
||||||
|
this.visibleValue = hasChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleValueChanged(visible) {
|
||||||
|
if (this.hasContainerTarget) {
|
||||||
|
this.containerTarget.classList.toggle(this.hiddenClass, false === visible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,8 @@ class BookingDataProcessor
|
|||||||
...$participant->skiPass,
|
...$participant->skiPass,
|
||||||
...$participant->board,
|
...$participant->board,
|
||||||
...$participant->rentals,
|
...$participant->rentals,
|
||||||
|
...$participant->parking,
|
||||||
|
...$participant->rentalInsurance,
|
||||||
];
|
];
|
||||||
foreach ($servicesToMap as $service) {
|
foreach ($servicesToMap as $service) {
|
||||||
if (false === isset($this->additionalServices[$service->id])) {
|
if (false === isset($this->additionalServices[$service->id])) {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ class Service
|
|||||||
public const TOKEN_ADDITIONAL = 'SON';
|
public const TOKEN_ADDITIONAL = 'SON';
|
||||||
public const TOKEN_BOARD = 'VPF';
|
public const TOKEN_BOARD = 'VPF';
|
||||||
public const TOKEN_RENTALS = ['VER', 'VE2', 'VE3', 'VE4', 'VE5', 'VE6', 'VE7', 'VE8'];
|
public const TOKEN_RENTALS = ['VER', 'VE2', 'VE3', 'VE4', 'VE5', 'VE6', 'VE7', 'VE8'];
|
||||||
|
public const TOKEN_PARKING = 'PAR';
|
||||||
|
public const TOKEN_RENTAL_INSURANCE = 'LVS';
|
||||||
|
|
||||||
public const STATUS_AVAILABLE = 'Frei';
|
public const STATUS_AVAILABLE = 'Frei';
|
||||||
public const STATUS_BLOCKED = 'Buchungsstop';
|
public const STATUS_BLOCKED = 'Buchungsstop';
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ final class BookingType extends AbstractType
|
|||||||
'selectable_services' => $this->mergeSelectableServices($data, Service::TOKEN_ADDITIONAL),
|
'selectable_services' => $this->mergeSelectableServices($data, Service::TOKEN_ADDITIONAL),
|
||||||
'selectable_board' => $this->mergeSelectableServices($data, Service::TOKEN_BOARD),
|
'selectable_board' => $this->mergeSelectableServices($data, Service::TOKEN_BOARD),
|
||||||
'selectable_rentals' => $this->mergeSelectableServices($data, Service::TOKEN_RENTALS),
|
'selectable_rentals' => $this->mergeSelectableServices($data, Service::TOKEN_RENTALS),
|
||||||
|
'selectable_parking' => $this->mergeSelectableServices($data, Service::TOKEN_PARKING),
|
||||||
|
'selectable_rental_insurance' => $this->mergeSelectableServices($data, Service::TOKEN_RENTAL_INSURANCE),
|
||||||
'selectable_transportation_services_to' => $travelData
|
'selectable_transportation_services_to' => $travelData
|
||||||
->getTransportationServicesByDirection('HIN', false),
|
->getTransportationServicesByDirection('HIN', false),
|
||||||
'selectable_transportation_services_fro' => $travelData
|
'selectable_transportation_services_fro' => $travelData
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ class BookingData
|
|||||||
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_BOARD);
|
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_BOARD);
|
||||||
$participantData->rentals = $booking
|
$participantData->rentals = $booking
|
||||||
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_RENTALS);
|
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_RENTALS);
|
||||||
|
$participantData->parking = $booking
|
||||||
|
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_PARKING);
|
||||||
|
$participantData->rentalInsurance = $booking
|
||||||
|
->getAdditionalServicesForParticipantByGroup($index, Service::TOKEN_RENTAL_INSURANCE);
|
||||||
|
|
||||||
// Different keys for direction used in booking data (H <=> HIN, R <=> RUECK)!
|
// Different keys for direction used in booking data (H <=> HIN, R <=> RUECK)!
|
||||||
$participantData->transportationServiceTo = $booking
|
$participantData->transportationServiceTo = $booking
|
||||||
->getTransportationServiceForParticipantAndDirection($index, 'H');
|
->getTransportationServiceForParticipantAndDirection($index, 'H');
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ class ParticipantData
|
|||||||
public array $skiPass = [];
|
public array $skiPass = [];
|
||||||
public array $board = [];
|
public array $board = [];
|
||||||
public array $rentals = [];
|
public array $rentals = [];
|
||||||
|
public array $parking = [];
|
||||||
|
public array $rentalInsurance = [];
|
||||||
public ?Service $transportationServiceTo = null;
|
public ?Service $transportationServiceTo = null;
|
||||||
public ?Service $transportationServiceFro = null;
|
public ?Service $transportationServiceFro = null;
|
||||||
public ?Pickup $pickup = null;
|
public ?Pickup $pickup = null;
|
||||||
|
|||||||
@@ -233,13 +233,36 @@ class ParticipantType extends AbstractType
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (0 < count($options['selectable_rentals'])) {
|
if (0 < count($options['selectable_rentals'])) {
|
||||||
|
// Extend common options for rentals to add checkbox-toggle action
|
||||||
|
$rentalChoiceAttr = $commonChoiceFieldOptions['choice_attr'];
|
||||||
$form->add('rentals', ChoiceType::class, [
|
$form->add('rentals', ChoiceType::class, [
|
||||||
...$commonChoiceFieldOptions,
|
...$commonChoiceFieldOptions,
|
||||||
'label' => 'Verleih',
|
'label' => 'Verleih',
|
||||||
'choices' => $options['selectable_rentals'],
|
'choices' => $options['selectable_rentals'],
|
||||||
|
'choice_attr' => function (?Service $service) use ($rentalChoiceAttr) {
|
||||||
|
$attributes = $rentalChoiceAttr($service);
|
||||||
|
// Add checkbox-toggle action to existing actions
|
||||||
|
$attributes['data-action'] = 'booking#toggle checkbox-toggle#check';
|
||||||
|
|
||||||
|
return $attributes;
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parking - single checkbox (always add, JS will show/hide for PKW travelers)
|
||||||
|
$form->add('parking', ChoiceType::class, [
|
||||||
|
...$commonChoiceFieldOptions,
|
||||||
|
'label' => 'Parkplatz',
|
||||||
|
'choices' => $options['selectable_parking'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Rental Insurance - single checkbox (always add, JS will show/hide when rentals selected)
|
||||||
|
$form->add('rentalInsurance', ChoiceType::class, [
|
||||||
|
...$commonChoiceFieldOptions,
|
||||||
|
'label' => 'Leihausrüstungsversicherung',
|
||||||
|
'choices' => $options['selectable_rental_insurance'],
|
||||||
|
]);
|
||||||
|
|
||||||
// Transportation
|
// Transportation
|
||||||
$transportationChoiceAttributes = function (?Service $service) use ($options) {
|
$transportationChoiceAttributes = function (?Service $service) use ($options) {
|
||||||
$attributes = [
|
$attributes = [
|
||||||
@@ -330,12 +353,39 @@ class ParticipantType extends AbstractType
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get selected transportation service
|
||||||
$transportationId = $data['transportationServiceTo'] ?? null;
|
$transportationId = $data['transportationServiceTo'] ?? null;
|
||||||
$transportation = $options['travel']->pickups[$transportationId] ?? null;
|
$transportation = null;
|
||||||
|
foreach ($options['selectable_transportation_services_to'] as $service) {
|
||||||
|
if ($service->id == $transportationId) {
|
||||||
|
$transportation = $service;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pickup field for PKW (existing logic, corrected)
|
||||||
if (null !== $transportation && 'PKW' === $transportation->subType) {
|
if (null !== $transportation && 'PKW' === $transportation->subType) {
|
||||||
$form->remove('pickup');
|
if ($form->has('pickup')) {
|
||||||
unset($data['pickup']);
|
$form->remove('pickup');
|
||||||
|
unset($data['pickup']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove parking field for BUS travelers (only show for PKW)
|
||||||
|
if (null !== $transportation && 'BUS' === $transportation->subType) {
|
||||||
|
if ($form->has('parking')) {
|
||||||
|
$form->remove('parking');
|
||||||
|
unset($data['parking']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove rental insurance if no rentals selected
|
||||||
|
$hasRentals = isset($data['rentals']) && is_array($data['rentals']) && 0 < count($data['rentals']);
|
||||||
|
if (false === $hasRentals) {
|
||||||
|
if ($form->has('rentalInsurance')) {
|
||||||
|
$form->remove('rentalInsurance');
|
||||||
|
unset($data['rentalInsurance']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter selectable services by participant's age
|
// filter selectable services by participant's age
|
||||||
@@ -389,6 +439,7 @@ class ParticipantType extends AbstractType
|
|||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -402,6 +453,8 @@ class ParticipantType extends AbstractType
|
|||||||
'selectable_services' => [],
|
'selectable_services' => [],
|
||||||
'selectable_board' => [],
|
'selectable_board' => [],
|
||||||
'selectable_rentals' => [],
|
'selectable_rentals' => [],
|
||||||
|
'selectable_parking' => [],
|
||||||
|
'selectable_rental_insurance' => [],
|
||||||
'selectable_transportation_services_to' => [],
|
'selectable_transportation_services_to' => [],
|
||||||
'selectable_transportation_services_fro' => [],
|
'selectable_transportation_services_fro' => [],
|
||||||
'selectable_pickups' => [],
|
'selectable_pickups' => [],
|
||||||
|
|||||||
@@ -194,7 +194,7 @@
|
|||||||
<h3 class="text-xl font-semibold">
|
<h3 class="text-xl font-semibold">
|
||||||
Leistungen
|
Leistungen
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-4" {{ stimulus_controller('checkbox-toggle', { 'visible': participant.rentals|length > 0 }, { 'hidden': 'hidden' }) }}>
|
||||||
{% if child.courses is defined %}
|
{% if child.courses is defined %}
|
||||||
{{ form_row(child.courses) }}
|
{{ form_row(child.courses) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -208,8 +208,16 @@
|
|||||||
{{ form_row(child.board) }}
|
{{ form_row(child.board) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if child.rentals is defined %}
|
{% if child.rentals is defined %}
|
||||||
{{ form_row(child.rentals) }}
|
<div {{ stimulus_target('checkbox-toggle', 'checkboxGroup') }}>
|
||||||
|
{{ form_row(child.rentals) }}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div {{ stimulus_target('select-toggle--parking', 'container') }}>
|
||||||
|
{{ form_row(child.parking) }}
|
||||||
|
</div>
|
||||||
|
<div {{ stimulus_target('checkbox-toggle', 'container') }}>
|
||||||
|
{{ form_row(child.rentalInsurance) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if not travelData.additionalServicesMutable %}
|
{% if not travelData.additionalServicesMutable %}
|
||||||
<div class="absolute inset-0 cursor-not-allowed"></div>
|
<div class="absolute inset-0 cursor-not-allowed"></div>
|
||||||
@@ -220,7 +228,7 @@
|
|||||||
Hin-/Rückreise
|
Hin-/Rückreise
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-4">
|
||||||
<div class="space-y-2" {{ stimulus_controller('select-toggle', { 'show': ['BUS'], 'visible': participant.transportationServiceTo is not null and participant.transportationServiceTo.subType == 'BUS' }, { 'hidden': 'hidden' }) }}>
|
<div class="space-y-2" {{ stimulus_controller('select-toggle', { 'show': ['BUS'], 'visible': participant.transportationServiceTo is not null and participant.transportationServiceTo.subType == 'BUS' }, { 'hidden': 'hidden' }) }} {{ stimulus_controller('select-toggle--parking', { 'show': ['PKW'], 'visible': participant.transportationServiceTo is not null and participant.transportationServiceTo.subType == 'PKW' }, { 'hidden': 'hidden' }) }}>
|
||||||
{{ form_row(child.transportationServiceTo) }}
|
{{ form_row(child.transportationServiceTo) }}
|
||||||
{% if child.pickup is defined %}
|
{% if child.pickup is defined %}
|
||||||
<div {{ stimulus_target('select-toggle', 'container') }}>
|
<div {{ stimulus_target('select-toggle', 'container') }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user