diff --git a/public/typo3conf/ext/ep_products/Classes/Service/DateService.php b/public/typo3conf/ext/ep_products/Classes/Service/DateService.php index dabe960a..e2dddef3 100644 --- a/public/typo3conf/ext/ep_products/Classes/Service/DateService.php +++ b/public/typo3conf/ext/ep_products/Classes/Service/DateService.php @@ -60,6 +60,18 @@ class DateService implements SingletonInterface */ protected $uriBuilder; + /** + * @var string[] + */ + protected $serviceSections = [ + 'BUS' => 'Anreise', + 'VPF' => 'Verpflegung', + 'SPA' => 'Skipass', + 'KUR' => 'Kurse', + 'VER' => 'Verleih', + 'SON' => 'Sonstiges', + ]; + /** * @param ProductRepository $productRepository * @param DateRepository $dateRepository @@ -303,6 +315,7 @@ class DateService implements SingletonInterface 'paCode' => $paCode, 'isDayTrip' => $isDayTrip, ]); + $optionalServices = $this->preprocessOptionalServices(unserialize($row['roomOptionalServices'])); $priceTable[] = [ 'dateStart' => $dateStart->format('d.m.Y'), 'dateEnd' => $dateEnd->format('d.m.Y'), @@ -316,7 +329,8 @@ class DateService implements SingletonInterface 'roomBusPrice' => $row['busPrice'], 'roomDiscount' => $row['roomDiscount'], 'roomNights' => $row['roomNights'], - 'roomOptionalServices' => unserialize($row['roomOptionalServices']), + 'roomOptionalServices' => $optionalServices, + 'roomHasOptionalServices' => count($optionalServices) > 0, 'dateBusProId' => $row['dateBusProId'], 'hotelBusProId' => $row['hotelBusProId'], 'roomBusProId' => $row['roomBusProId'], @@ -329,6 +343,23 @@ class DateService implements SingletonInterface return $priceTable; } + public function preprocessOptionalServices(array $servicesFlat): array + { + $processed = []; + + foreach ($servicesFlat as $key => $services) { + if (!array_key_exists($key, $processed)) { + $processed[$key] = [ + 'label' => $this->serviceSections[$key], + 'services' => [], + ]; + $processed[$key]['services'] += $services; + } + } + + return $processed; + } + /** * @param array $options * @return array diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/_app.js b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/_app.js index 52a09284..ed3fc484 100644 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/_app.js +++ b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/_app.js @@ -24,6 +24,7 @@ import ajaxContent from './components/ajax-content' import faq from './components/faq' import osmMap from './components/osm-map' import productDetail from './components/product-detail' +import daytripDateSelect from './components/daytrip-date-select' // Executed on document ready document.addEventListener('DOMContentLoaded', () => { @@ -35,6 +36,7 @@ document.addEventListener('DOMContentLoaded', () => { Alpine.store('show', { searchBox: false, mobileNav: false, + modal: null, }) Alpine.store('watchlist', storedWatchlist) Alpine.data('scrollTop', scrollTop) @@ -47,6 +49,7 @@ document.addEventListener('DOMContentLoaded', () => { Alpine.data('faq', faq) Alpine.data('osmMap', osmMap) Alpine.data('productDetail', productDetail) + Alpine.data('daytripDateSelect', daytripDateSelect) Alpine.start() Glightbox({ diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/DateSelect.vue b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/DateSelect.vue deleted file mode 100644 index efc3dc7d..00000000 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/DateSelect.vue +++ /dev/null @@ -1,272 +0,0 @@ - - - - - diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/DatesTable.vue b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/DatesTable.vue deleted file mode 100644 index 44732f12..00000000 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/DatesTable.vue +++ /dev/null @@ -1,225 +0,0 @@ - - - diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/PriceTable.vue b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/PriceTable.vue deleted file mode 100644 index 8af16f8b..00000000 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/PriceTable.vue +++ /dev/null @@ -1,211 +0,0 @@ - - - diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/ProductDetail.vue b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/ProductDetail.vue deleted file mode 100644 index 4fb05735..00000000 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/ProductDetail.vue +++ /dev/null @@ -1,215 +0,0 @@ - diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/ServicesListIncluded.vue b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/ServicesListIncluded.vue deleted file mode 100644 index 55ed51aa..00000000 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/ServicesListIncluded.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/ServicesListOptional.vue b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/ServicesListOptional.vue deleted file mode 100644 index b30b0a50..00000000 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/ServicesListOptional.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/daytrip-date-select.js b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/daytrip-date-select.js new file mode 100644 index 00000000..08bc0998 --- /dev/null +++ b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/daytrip-date-select.js @@ -0,0 +1,153 @@ +import axios from 'axios' +import flatpickr from 'flatpickr' +import { German } from 'flatpickr/dist/l10n/de' +import dayjs from 'dayjs' +import 'dayjs/locale/de' + +export default props => ({ + hotelName: props.hotelName, + hotelUid: props.hotelUid, + productUid: props.productUid, + contingentEndpointUri: props.contingentEndpointUri, + roomsEndpointUri: props.roomsEndpointUri, + loading: false, + picker: null, + displayedFrom: dayjs().startOf('month'), + displayedTo: dayjs().endOf('month'), + selectedFrom: null, + selectedTo: null, + selectedNumberOfNights: 0, + minNightsForSelectedDate: 0, + availableDates: [], + availableRooms: [], + init() { + this.picker = flatpickr(this.$refs.field, { + mode: 'range', + dateFormat: 'Y-m-d', + locale: German, + inline: true, + onChange: selectedDates => { + this.availableRooms = [] + if (selectedDates.length === 2) { + this.selectedFrom = dayjs(selectedDates[0]) + this.selectedTo = dayjs(selectedDates[1]) + this.selectedNumberOfNights = this.getSelectedNumberOfNights() + this.minNightsForSelectedDate = this.getMinNightsForSelectedDate() + if (this.selectedNumberOfNights >= this.minNightsForSelectedDate) { + this.fetchAvailableRooms() + } + } + }, + onDayCreate: (dObj, dStr, fp, dayElem) => { + const day = dayElem.dateObj.getFullYear() + '-' + ('0' + (dayElem.dateObj.getMonth()+1)).slice(-2) + + '-' + ('0' + dayElem.dateObj.getDate()).slice(-2) + if (day in this.availableDates) { + dayElem.classList.add('available') + } + } + }) + this.$nextTick(() => { + this.fetchEnabledDates() + }) + }, + headerLabel () { + let label = 'Buchungsoptionen' + if (this.hotelName) { + label += ' ' + this.hotelName + } + if (this.selectedFrom && this.selectedTo) { + label += ' ' + this.selectedFrom.format('DD.MM.YY') + ' - ' + this.selectedTo.format('DD.MM.YY') + label += ' (' + this.selectedNumberOfNights + ' Nächte)' + } + return label + }, + message() { + if (this.loading) { + return '' + } + if (this.selectedFrom === null || this.selectedTo === null) { + return 'Bitte einen Zeitraum auswählen.' + } + if (this.selectedNumberOfNights < this.minNightsForSelectedDate) { + return 'Bitte mindestens ' + this.minNightsForSelectedDate + ' Nächte auswählen.' + } + if (this.availableRooms.length === 0) { + return 'Im gewählten Zeitraum sind leider keine Zimmer verfügbar.' + } + return '' + }, + onDateSelected(date) { + this.selectedDate = dayjs(date) + }, + fetchEnabledDates() { + this.loading = true + const data = new FormData() + data.set('tx_epproducts_ajax[hotel]', this.hotelUid) + data.set('tx_epproducts_ajax[product]', this.productUid) + axios({ + url: this.contingentEndpointUri, + method: 'post', + data + }).then(response => { + const json = response.data + if (json) { + this.availableDates = json + const enabledDates = [] + for (const date in this.availableDates) { + if (this.availableDates.hasOwnProperty(date)) { + enabledDates.push(this.availableDates[date].date) + } + } + this.picker.set('enable', enabledDates) + this.picker.set('minDate', enabledDates[0]) + this.picker.set('maxDate', enabledDates[enabledDates.length - 1]) + this.picker.jumpToDate(enabledDates[0]) + } + }).finally(() => { + this.loading = false + }) + }, + fetchAvailableRooms() { + this.loading = true + const data = new FormData() + data.set('tx_epproducts_ajax[hotel]', this.hotelUid) + data.set('tx_epproducts_ajax[product]', this.productUid) + data.set('tx_epproducts_ajax[dateFrom]', this.selectedFrom.format('YYYY-MM-DD')) + data.set('tx_epproducts_ajax[dateTo]', this.selectedTo.format('YYYY-MM-DD')) + axios({ + url: this.roomsEndpointUri, + method: 'post', + data + }).then(response => { + this.availableRooms = response.data + }).finally(() => { + this.loading = false + }) + }, + calculatePrice (roomCode) { + const room = this.availableRooms.find(obj => { + return obj.code === roomCode + }); + if (room) { + const nights = this.getSelectedNumberOfNights() + return room.price + (nights - room.minNights) * room.additionalNightPrice + } + return null; + }, + getMinNightsForSelectedDate () { + if (this.selectedFrom === null) { + return 0 + } + const key = this.selectedFrom.format('YYYY-MM-DD') + if (!key in this.availableDates) { + return 0 + } + return this.availableDates[key].minNights + }, + getSelectedNumberOfNights () { + if (this.selectedFrom === null || this.selectedTo === null) { + return 0 + } + return this.selectedTo.diff(this.selectedFrom, 'days'); + } +}) diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/product-detail.js b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/product-detail.js index 836dc7f4..f4f52287 100644 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/product-detail.js +++ b/public/typo3conf/ext/ep_theme/Resources/Private/Assets/js/components/product-detail.js @@ -21,6 +21,9 @@ export default uris => ({ showAll: false, init() { this.loadDates() + this.$watch('tableMode', () => { + this.$el.scrollIntoView({ block: 'start', behavior: 'smooth' }) + }) }, loadDates() { this.loading = true diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Partials/Modal.html b/public/typo3conf/ext/ep_theme/Resources/Private/Partials/Modal.html new file mode 100644 index 00000000..20298dc8 --- /dev/null +++ b/public/typo3conf/ext/ep_theme/Resources/Private/Partials/Modal.html @@ -0,0 +1,22 @@ + + +
+
+
+
+ + +
+ {content -> f:format.raw()} +
+
+ + diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Templates/AjaxDate/Dates.html b/public/typo3conf/ext/ep_theme/Resources/Private/Templates/AjaxDate/Dates.html deleted file mode 100644 index 46973206..00000000 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Templates/AjaxDate/Dates.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - -
- - - -
Buchungsoptionen{f:if(condition: hotelName, then: ' {hotelName}')}
-
- -
Termine & Leistungen
-
-
-
- - - -
- - - - - - - - - - - - - 4', then: ' x-bind:class="{ \'hidden\': !showAll }"')}> - - - - - - - - -
Termin - - - - Preis abinkl.
- - - - - - - {row.dateStart} - {row.dateEnd} - - - - {row.dateNights} - - - - {row.datePseudoPrice} € - - - - - - - - -
-
- - - - - -
-
-
- - diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Templates/AjaxTable/Pricetable.html b/public/typo3conf/ext/ep_theme/Resources/Private/Templates/AjaxTable/Pricetable.html deleted file mode 100644 index d80927ab..00000000 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Templates/AjaxTable/Pricetable.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - -
-
-
Buchungsoptionen
-
{dateStart} - {dateEnd}
-
- - - -
- - - - - - - - - - - - - - - - - - - - - -
ZimmertypPreisinkl.
- - - {row.roomName} - - - - {row.roomName} - - - - - - - - - - - - - - - - - - - - - - - -
-
- -

- -

-
-
-
-
Inklusivleistungen
-
-
    - -
  • {service}
  • -
    -
-
-
-
-
-
- - diff --git a/public/typo3conf/ext/ep_theme/Resources/Private/Templates/Product/Detail.html b/public/typo3conf/ext/ep_theme/Resources/Private/Templates/Product/Detail.html index 117a3f36..7921614a 100644 --- a/public/typo3conf/ext/ep_theme/Resources/Private/Templates/Product/Detail.html +++ b/public/typo3conf/ext/ep_theme/Resources/Private/Templates/Product/Detail.html @@ -11,8 +11,6 @@
", - contingents: "{ep:uri.ajax(action: 'list', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}", - rooms: "{ep:uri.ajax(action: 'rooms', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}", pricetable: "{ep:uri.ajax(action: 'pricetable', controller: 'AjaxTable', arguments: '{product: product, hotel: hotel}', format: 'html', pageUid: settings.defaultAjaxUid)}", travelAlert: "{f:uri.typolink(parameter: settings.travelAlertPageUid)}" })'> @@ -133,7 +131,7 @@
-
+
Loading...
@@ -153,17 +151,7 @@ - +
@@ -250,9 +238,8 @@ -
-
Buchungsoptionen {hotel.name}
-
Termine & Leistungen
+
+
Buchungsoptionen {hotel.name}
@@ -304,10 +291,10 @@ - -
- - - - - - +
+