Finalize migration of product detail page

This commit is contained in:
Björn Fromme
2021-07-27 14:06:12 +02:00
committed by Björn Fromme
parent 5a63f88153
commit 06c67b4fad
15 changed files with 364 additions and 1356 deletions
@@ -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
@@ -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({
@@ -1,272 +0,0 @@
<template>
<div class="date-select-wrapper">
<div class="ep-headbar">{{ headerLabel }}</div>
<div class="row">
<div class="col-xs-12 col-md-6">
<div class="date-select">
<div class="date-select__picker">
<div class="daterange__panel">
<input ref="field" type="hidden">
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6">
<table class="table table-striped pricetable" v-show="availableRooms.length">
<thead>
<tr>
<th>Zimmerart</th>
<th>Preis</th>
<th>inkl.</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) of availableRooms">
<td>{{ row.label }}</td>
<td style="white-space: nowrap">ab {{ calculatePrice(row.code) }} &euro;</td>
<td>
<i v-if="row.skipassIncluded" aria-label="Skipass inklusive" role="tooltip" data-microtip-position="top" class="fa fa-tag"></i>
<a href="#" data-toggle="modal"
:data-target="'#services' + index"
data-microtip-position="top" aria-label="Inklusivleistungen zeigen" role="tooltip">
<i class="fa fa-info-circle"></i>
</a>
<div class="modal fade" :id="'services' + index" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title">Inklusivleistungen</h5>
</div>
<div class="modal-body">
<div class="table-responsive">
<table class="table table-striped">
<tbody>
<tr v-for="service of row.servicesIncluded">
<td><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {{ service }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</td>
<td>
<a :href="row.bookingUrl" target="_blank"
class="button button--small button--action">Buchen</a>
</td>
</tr>
</tbody>
</table>
<div v-show="loading" class="loader">
Loading... <img :src="loaderUri" alt="Loading" title="Loading">
</div>
<div v-show="message">{{ message }}</div>
</div>
</div>
</div>
</template>
<script>
import $ from 'jquery'
import axios from 'axios';
import Vue from 'vue'
import EventBus from '../_bus'
import flatpickr from 'flatpickr'
import { German } from 'flatpickr/dist/l10n/de'
import dayjs from 'dayjs'
import 'dayjs/locale/de'
export default {
data () {
return {
loading: false,
picker: null,
displayedFrom: dayjs().startOf('month'),
displayedTo: dayjs().endOf('month'),
selectedFrom: null,
selectedTo: null,
selectedNumberOfNights: 0,
minNightsForSelectedDate: 0,
availableDates: [],
availableRooms: []
}
},
props: {
hotelName: {
type: String,
required: true
},
hotelUid: {
type: Number,
required: true
},
productUid: {
type: Number,
required: true
},
contingentEndpointUri: {
type: String,
required: true
},
roomsEndpointUri: {
type: String,
required: true
},
argumentPrefix: {
type: String,
required: true
},
loaderUri: {
type: String,
required: true
}
},
computed: {
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 '';
}
},
methods: {
onDateSelected (date) {
this.selectedDate = dayjs(date);
},
fetchEnabledDates () {
let query = {};
query[this.argumentPrefix + '[hotel]'] = this.hotelUid;
query[this.argumentPrefix + '[product]'] = this.productUid;
this.loading = true;
axios({
url: this.contingentEndpointUri,
method: 'post',
data: $.param(query)
}).then(response => {
const json = response.data;
this.loading = false;
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]);
}
});
},
fetchAvailableRooms () {
let query = {};
query[this.argumentPrefix + '[hotel]'] = this.hotelUid;
query[this.argumentPrefix + '[product]'] = this.productUid;
query[this.argumentPrefix + '[dateFrom]'] = this.selectedFrom.format('YYYY-MM-DD');
query[this.argumentPrefix + '[dateTo]'] = this.selectedTo.format('YYYY-MM-DD');
this.loading = true;
axios({
url: this.roomsEndpointUri,
method: 'post',
data: $.param(query)
}).then(response => {
const json = response.data;
this.loading = false;
this.availableRooms = json;
EventBus.$emit('tableLoaded');
});
},
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');
}
},
mounted () {
this.picker = $(this.$refs.field).flatpickr({
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');
}
}
});
Vue.nextTick(() => {
this.fetchEnabledDates();
});
}
}
</script>
<style scoped>
.date-select-wrapper {
padding-bottom: 16px;
}
</style>
@@ -1,225 +0,0 @@
<template>
<div class="dates-table-wrapper">
<template v-if="showHeader">
<div v-if="bookable" class="ep-headbar">{{ headerLabel }}</div>
<div v-if="!bookable" class="ep-headbar">Termine &amp; Leistungen</div>
</template>
<div class="dates-table__pricetoggle" v-if="hasSurcharge || hasDiscount">
<a href="#" v-if="hasSurcharge" @click.prevent="toggleBus">
<template v-if="addBus">
<i class="fa fa-arrow-circle-right"></i> Preise für Eigenanreise anzeigen
</template>
<template v-else>
<i class="fa fa-arrow-circle-right"></i> Preise für Busanreise anzeigen
</template>
</a>
<a href="#" v-if="hasDiscount" @click.prevent="toggleDiscount">
<template v-if="discountBus">
<i class="fa fa-arrow-circle-right"></i> Preise für Busanreise anzeigen
</template>
<template v-else>
<i class="fa fa-arrow-circle-right"></i> Preise für Eigenanreise anzeigen
</template>
</a>
</div>
<div class="table-responsive">
<table class="table table-striped dates-table">
<thead>
<tr>
<th>Termin</th>
<th>
<span data-microtip-position="bottom" aria-label="Nächte" role="tooltip">
<i class="fa fa-bed"></i>
</span>
</th>
<th>Preis ab</th>
<th>inkl.</th>
<th class="hidden-xs"></th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) of datesRows" :class="{ 'hidden': index > 4 && !showAll }">
<td class="dates-table__cell dates-table__cell--date">
<template v-if="bookable && row.available && !row.isHideBookingButton">
<a class="dates-table__date" href="#"
@click.prevent="loadPriceTable(row.dateUid)">
{{ row.dateStart }} - {{ row.dateEnd }}
</a>
<div class="hidden-md hidden-lg dates-table__links">
<a href="#" @click.prevent="loadPriceTable(row.dateUid)" class="button button--small">
Details
</a>
<a :href="row.bookingUrl" target="_blank" class="button button--small button--action">
Buchen
</a>
</div>
</template>
<template v-else>{{ row.dateStart }} - {{ row.dateEnd }}</template>
</td>
<td class="dates-table__cell dates-table__cell--nights">{{ row.dateNights }}</td>
<td class="dates-table__cell dates-table__cell--price">
<span class="dates-table__pseudo-price" v-if="row.datePseudoPrice">{{ row.datePseudoPrice }} </span>
<strong>{{ calculatePrice(row) }} </strong>
</td>
<td class="dates-table__cell dates-table__cell--icons">
<span class="icon-wrapper hidden-xs hidden-sm">
<span v-if="showBusIcon(row)" data-microtip-position="top" aria-label="Busfahrt inklusive" role="tooltip">
<i class="fa fa-bus"></i>
</span>
<span v-if="row.dateSkipassIncluded" data-microtip-position="top" :aria-label="row.season === 's' ? 'Bergbahnticket inklusive' : 'Skipass inklusive'" role="tooltip">
<i class="fa fa-tag"></i>
</span>
</span>
<button type="button" class="button button--light button--small hidden-xs"
data-toggle="modal" :data-target="'#dateservices' + index"
data-microtip-position="top" aria-label="Alle Inklusivleistungen anzeigen" role="tooltip">Leistungen</button>
<a href="#" data-toggle="modal" :data-target="'#dateservices' + index"
class="hidden-sm hidden-md hidden-lg"><i class="fa fa-info-circle"></i></a>
</td>
<td class="dates-table__cell dates-table__cell--buttons hidden-xs">
<button v-if="row.available || row.isHideBookingButton" class="button button--small" @click="loadPriceTable(row.dateUid)">Details</button>
<a :href="row.bookingUrl" target="_blank" v-if="row.available && !row.isHideBookingButton" class="button button--small button--action">Buchen</a>
<a :href="travelAlertUrl" v-if="row.isHideBookingButton" class="button button--small button--mute">Reisen-Alert</a>
<span v-if="!row.available && !row.isHideBookingButton" class="label label-default">ausgebucht</span>
</td>
</tr>
</tbody>
</table>
</div>
<div class="row dates-table__links" v-if="datesRows.length > 5 || altProductLink">
<div class="col-xs-12 col-md-6">
<a href="#" v-if="datesRows.length > 5" @click.prevent="toggleAllDates()">
<i class="fa" :class="[{ 'fa-minus-circle': showAll }, { 'fa-plus-circle': !showAll }]"></i>
<template v-if="showAll">Weniger Termine anzeigen</template><template v-else>Alle Termine anzeigen</template>
</a>
</div>
<div class="col-xs-12 col-md-6">
<a :href="altProductLink" v-if="altProductLink" :title="altLabel" class="dates-table__altlink">
<i class="fa fa-arrow-circle-right"></i> {{ altLabel }}
</a>
</div>
</div>
<template v-for="(row, index) of datesRows">
<div class="modal fade" :id="'dateservices' + index" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
<h5 class="modal-title">Inklusivleistungen</h5>
</div>
<div class="modal-body">
<div class="table-responsive">
<services-included :services="row.dateServicesIncluded" class="services"></services-included>
</div>
</div>
</div>
</div>
</div>
</template>
</div>
</template>
<script>
import $ from 'jquery'
import EventBus from '../_bus'
import ServicesIncluded from './ServicesListIncluded.vue'
export default {
components: {
ServicesIncluded
},
props: {
datesRows: {
required: true
},
hotelName: {
default: null
},
bookable: {
type: Boolean,
default: true
},
altProductLink: {
type: String,
default: null
},
altLabel: {
type: String,
default: null
},
showHeader: {
type: Boolean,
default: true
},
travelAlertUrl: {
type: String,
required: true
}
},
data () {
return {
showAll: false,
addBus: false,
discountBus: false,
}
},
computed: {
headerLabel () {
let label = 'Buchungsoptionen';
if (this.hotelName) {
label += ' ' + this.hotelName;
}
return label;
},
hasSurcharge () {
let result = false;
$.each(this.datesRows, (index, row) => {
if (row.dateBusPrice > 0) {
result = true;
return false;
}
});
return result;
},
hasDiscount () {
let result = false;
$.each(this.datesRows, (index, row) => {
if (row.dateDiscount < 0) {
result = true;
return false;
}
});
return result;
}
},
methods: {
loadPriceTable (dateUid) {
this.showAll = false;
EventBus.$emit('loadPriceTable', dateUid);
},
toggleBus () {
this.addBus = !this.addBus;
},
toggleDiscount () {
this.discountBus = !this.discountBus;
},
toggleAllDates () {
this.showAll = !this.showAll;
},
calculatePrice (row) {
if (this.addBus) {
return row.dateMinPrice + row.dateBusPrice;
} else if (this.discountBus) {
return row.dateMinPrice + row.dateDiscount;
}
return row.dateMinPrice;
},
showBusIcon (row) {
return (
row.dateBusPrice && this.addBus ||
row.dateBusIncluded && !this.discountBus
);
}
}
}
</script>
@@ -1,211 +0,0 @@
<template>
<div class="price-table-wrapper">
<div class="row ep-headbar" v-if="showHeader">
<div class="col-xs-6">Buchungsoptionen</div>
<div class="col-xs-6 text-right">{{dateLabel}}</div>
</div>
<div class="dates-table__pricetoggle" v-if="hasSurcharge || hasDiscount">
<a href="#" v-if="hasSurcharge" @click.prevent="toggleBus">
<template v-if="addBus">
<i class="fa fa-car"></i> Preise für Eigenanreise anzeigen
</template>
<template v-else>
<i class="fa fa-bus"></i> Preise für Busanreise anzeigen
</template>
</a>
<a href="#" v-if="hasDiscount" @click.prevent="toggleDiscount">
<template v-if="discountBus">
<i class="fa fa-bus"></i> Preise für Busanreise anzeigen
</template>
<template v-else>
<i class="fa fa-car"></i> Preise für Eigenanreise anzeigen
</template>
</a>
</div>
<div class="table-responsive">
<table class="table table-striped pricetable">
<thead>
<tr>
<th>Zimmertyp</th>
<th>Preis</th>
<th>inkl.</th>
<th></th>
<th class="hidden-xs hidden-md"></th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) of priceTableRows">
<td style="white-space: normal">
<template v-if="row.isHideBookingButton || !row.roomAvailable">
{{ row.roomName }}
</template>
<template v-else>
<a class="pricetable__room-label"
:href="row.bookingUrl" target="_blank">{{ row.roomName }}</a>
<div class="hidden-lg">
<a class="button button--small button--action"
:href="row.bookingUrl" target="_blank">Jetzt buchen</a>
</div>
</template>
<div class="hidden-lg" v-if="!row.roomAvailable">
<span class="label label-default">ausgebucht</span>
</div>
</td>
<td style="white-space: nowrap" :style="{'text-decoration': !row.roomAvailable && !row.isHideBookingButton ? 'line-through' : ''}">{{ calculatePrice(row) }} </td>
<td>
<span v-if="showBusIcon(row)" data-microtip-position="top" aria-label="Busfahrt inklusive" role="tooltip">
<i class="fa fa-bus"></i>
</span>
<span v-if="row.dateSkipassIncluded" data-microtip-position="top" :aria-label="row.season === 's' ? 'Bergbahnticket inklusive' : 'Skipass inklusive'" role="tooltip">
<i class="fa fa-tag"></i>
</span>
</td>
<td>
<button v-if="hasOptionalServices(row) && row.roomAvailable" type="button"
class="button button--light button--small hidden-xs"
data-toggle="modal" :data-target="'#optservices' + index"
data-microtip-position="top" aria-label="Alle Zusatzleistungen anzeigen" role="tooltip">Optionen</button>
<a href="#" v-if="hasOptionalServices(row) && row.roomAvailable" class="hidden-sm hidden-md hidden-lg"
data-toggle="modal" :data-target="'#optservices' + index"><i class="fa fa-info-circle"></i></a>
<div class="modal fade" :id="'optservices' + index" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
<h5 class="modal-title">Optionale Zusatzleistungen</h5>
</div>
<div class="modal-body">
<div class="table-responsive">
<services-optional :services="row.roomOptionalServices" class="services"></services-optional>
</div>
</div>
</div>
</div>
</div>
</td>
<td class="hidden-xs hidden-md">
<a class="button button--small button--action" v-if="row.roomAvailable && !row.isHideBookingButton"
:href="row.bookingUrl" target="_blank">Buchen</a>
<a class="button button--small button--mute" v-if="row.isHideBookingButton"
:href="travelAlertUrl">Reisen-Alert</a>
<span v-if="!row.roomAvailable && !row.isHideBookingButton" class="label label-default">ausgebucht</span>
</td>
</tr>
</tbody>
</table>
</div>
<p v-if="!singleDate">
<a class="button button--small"
href="#" @click.prevent="loadDatesTable()"><i class="fa fa-undo"></i> zurück zur Terminübersicht</a>
</p>
<div class="row">
<div class="col-md-12">
<div class="ep-headbar">Inklusivleistungen</div>
<div class="ep-box">
<ul class="list-unstyled">
<li v-for="service of servicesIncluded"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {{service}}</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import ServicesIncluded from './ServicesListIncluded.vue'
import ServicesOptional from './ServicesListOptional.vue'
import $ from 'jquery'
import EventBus from '../_bus'
export default {
components: {
ServicesIncluded,
ServicesOptional
},
props: {
priceTableRows: {
required: true
},
templateSuffix: {
type: String,
default: '_ep'
},
dateLabel: {
type: String,
default: ''
},
servicesIncluded: {
type: Array,
default: []
},
singleDate: {
type: Boolean,
default: false
},
showHeader: {
type: Boolean,
default: true
},
travelAlertUrl: {
type: String,
required: true
}
},
data () {
return {
addBus: false,
discountBus: false,
}
},
methods: {
loadDatesTable () {
EventBus.$emit('loadDatesTable');
},
hasOptionalServices (row) {
return !$.isEmptyObject(row.roomOptionalServices);
},
toggleBus () {
this.addBus = !this.addBus;
},
toggleDiscount () {
this.discountBus = !this.discountBus;
},
calculatePrice (row) {
if (this.addBus) {
return row.roomPrice + row.roomBusPrice;
} else if (this.discountBus) {
return row.roomPrice + row.roomDiscount;
}
return row.roomPrice;
},
showBusIcon (row) {
return (
row.roomBusPrice && this.addBus ||
row.dateBusIncluded && !this.discountBus
);
}
},
computed: {
hasSurcharge () {
let result = false;
$.each(this.priceTableRows, (index, row) => {
if (row.roomBusPrice > 0) {
result = true;
return false;
}
});
return result;
},
hasDiscount () {
let result = false;
$.each(this.priceTableRows, (index, row) => {
if (row.roomDiscount < 0) {
result = true;
return false;
}
});
return result;
}
}
}
</script>
@@ -1,215 +0,0 @@
<script>
import Vue from 'vue'
import $ from 'jquery'
import axios from 'axios';
import EventBus from '../_bus'
import DatesTable from './DatesTable.vue'
import PriceTable from './PriceTable.vue'
import ServicesIncluded from './ServicesListIncluded.vue'
import ServicesOptional from './ServicesListOptional.vue'
import DateSelect from './DateSelect.vue'
import DaterangeSelect from './DaterangeSelect.vue'
import Calendar from './Calendar.vue'
import OsmMap from './OsmMap.vue'
import dayjs from 'dayjs'
import 'dayjs/locale/de'
export default {
components: {
DatesTable,
PriceTable,
ServicesIncluded,
ServicesOptional,
DateSelect,
DaterangeSelect,
Calendar,
OsmMap,
},
data () {
return {
datesRows: {},
available: true,
altLabel: null,
altProductLink: null,
priceTableRows: {},
dateStart: '',
dateEnd: '',
servicesIncluded: [],
detailHtml: '',
bookable: true,
datesShow: false,
priceTableShow: false,
detailShow: false,
loading: false,
activeSection: null,
singleDate: false,
}
},
props: {
argumentPrefix: {
type: String,
required: true
},
uris: {
type: Object,
required: true
},
hotelFirst: {
type: Boolean,
default: false
},
busProId: {
type: Number,
default: 0
},
nameInternal: {
type: String,
default: ''
},
daytrip: {
type: Number,
default: 0
},
dateFrom: {
type: String,
default: null
},
dateTo: {
type: String,
default: null
}
},
computed: {
dateLabel () {
return this.dateStart + ' - ' + this.dateEnd;
}
},
methods: {
loadDates () {
this.loading = true;
this.datesShow = true;
this.priceTableShow = false;
this.detailShow = false;
const data = $.param({
'tx_epproducts_ajax[paCode]': this.$store.state.config.paCode,
'tx_epproducts_ajax[dateFrom]': this.dateFrom,
'tx_epproducts_ajax[dateTo]': this.dateTo
});
axios({
url: this.uris['dates'],
method: 'post',
data: data
}).then(response => {
const json = response.data;
if (json.dates.length === 1) {
this.singleDate = true;
let dateRow = json.dates[0];
this.loadPriceTableView(dateRow.dateUid);
} else {
this.singleDate = false;
this.datesRows = json.dates;
this.bookable = json.bookable;
this.altLabel = json.altLabel;
this.altProductLink = json.altProductLink;
}
this.available = json.dates.length > 0;
this.loading = false;
EventBus.$emit('tableLoaded');
});
},
loadDatesView (scroll = true) {
if (this.daytrip) {
this.loading = false;
this.datesShow = true;
} else {
this.loadDates();
}
this.activeSection = 'dates';
if (scroll) {
this.scrollToMain();
}
},
loadPriceTable (dateUid) {
this.loading = true;
this.datesShow = false;
this.priceTableShow = true;
this.detailShow = false;
let query = {};
query[this.argumentPrefix + '[date]'] = dateUid;
query[this.argumentPrefix + '[paCode]'] = this.$store.state.config.paCode;
axios({
url: this.uris['pricetable'],
method: 'post',
data: $.param(query)
}).then(response => {
const json = response.data;
this.priceTableRows = json.priceTable;
this.dateStart = dayjs(json.dateStart).format('DD.MM.YYYY');
this.dateEnd = dayjs(json.dateEnd).format('DD.MM.YYYY');
this.servicesIncluded = json.servicesIncluded;
this.loading = false;
EventBus.$emit('tableLoaded');
});
},
loadPriceTableView (dateuid) {
this.loadPriceTable(dateuid);
this.scrollToMain();
},
showHtml (section, scroll = true) {
this.datesShow = false;
this.priceTableShow = false;
this.detailShow = true;
if (scroll) {
this.scrollToMain();
}
this.activeSection = section;
},
scrollToMain () {
EventBus.$emit('scrollTo', '#main');
},
trackConversion () {
const dataLayer = window.dataLayer || [];
dataLayer.push({
'google_tag_params': {
'travel_destid': this.busProId,
'travel_originid': this.busProId,
'travel_pagetype': 'offerdetails',
'travel_startdate': this.dateStart,
'travel_enddate': this.dateEnd,
'travel_totalvalue': this.calculateMinPriceFromPriceTable() + '.00 EUR'
}
});
},
calculateMinPriceFromPriceTable () {
let minPrice = 0;
for (let index in this.priceTableRows) {
if (!this.priceTableRows.hasOwnProperty(index)) {
continue;
}
if (minPrice === 0 || this.priceTableRows[index].roomPrice < minPrice) {
minPrice = this.priceTableRows[index].roomPrice;
}
}
return minPrice;
}
},
created () {
EventBus.$on('loadDatesTable', () => {
this.loadDatesView();
});
EventBus.$on('loadPriceTable', (dateUid) => {
this.loadPriceTableView(dateUid);
});
},
mounted () {
Vue.nextTick(() => {
this.trackConversion();
if (this.hotelFirst) {
this.showHtml('hotel', false);
} else {
this.loadDatesView(false);
}
});
}
}
</script>
@@ -1,22 +0,0 @@
<template>
<table class="table table-striped">
<tbody>
<tr v-for="service of services">
<td><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {{ service }}</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
services: {
type: Array,
default () {
return []
}
}
}
}
</script>
@@ -1,49 +0,0 @@
<template>
<table class="table table-striped">
<tbody>
<template v-for="(section, key) of sections">
<template v-if="(key in services)">
<tr>
<th colspan="2"><strong>{{ section }}</strong></th>
</tr>
<tr v-for="service of services[key]">
<td>{{ service.label }}</td>
<td style="text-align: right; white-space: nowrap">{{ service.price }} </td>
</tr>
</template>
</template>
</tbody>
</table>
</template>
<script>
export default {
props: {
services: {
type: Object,
default () {
return {
BUS: [],
VPF: [],
SPA: [],
KUR: [],
VER: [],
SON: [],
}
}
}
},
data () {
return {
sections: {
BUS: 'Anreise',
VPF: 'Verpflegung',
SPA: 'Skipass',
KUR: 'Kurse',
VER: 'Verleih',
SON: 'Sonstiges',
}
}
}
}
</script>
@@ -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');
}
})
@@ -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
@@ -0,0 +1,22 @@
<html data-namespace-typo3-fluid="true" lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<div x-data x-show="$store.show.modal === '{id}'" x-transition x-cloak class="fixed inset-0 w-full h-full z-50">
<div class="absolute inset-0 w-full h-full bg-overlay-black" x-on:click="$store.show.modal = null"></div>
<div class="absolute top-1/2 left-1/2 transform -translate-xy-1/2 bg-white p-8 shadow w-full max-w-3xl rounded">
<div class="flex justify-between border-b border-gray-200 pb-2 mb-4">
<template x-if="$store.modal.title">
<span class="text-lg font-bold">
{title -> f:format.raw()}
</span>
</template>
<button x-on:click.prevent="$store.show.modal = null" class="inline-block">
<svg class="w-8 h-8"><use href="#icon-close"></use></svg>
</button>
</div>
{content -> f:format.raw()}
</div>
</div>
</html>
@@ -1,173 +0,0 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers"
xmlns:ep="http://typo3.org/ns/EP/EpTheme/ViewHelpers">
<f:layout name="Default"/>
<f:section name="Main">
<f:variable name="rowCount" value="{rows -> f:count()}"/>
<f:if condition="{rowCount} > 0">
<div class="dates-table-wrapper" x-data="datesTable">
<f:if condition="{showHeader}">
<f:if condition="{bookable}">
<f:then>
<div class="ep-headbar">Buchungsoptionen{f:if(condition: hotelName, then: ' {hotelName}')}</div>
</f:then>
<f:else>
<div class="ep-headbar">Termine &amp; Leistungen</div>
</f:else>
</f:if>
</f:if>
<f:if condition="{hasSourcharge} || {hasDiscount}">
<div class="dates-table__pricetoggle">
<f:if condition="{hasSurcharge}">
<a href="#" x-on:click.prevent="addBus = !addBus" x-show="addBus">
<i class="fa fa-arrow-circle-right"></i> Preise für Eigenanreise anzeigen
</a>
<a href="#" x-on:click.prevent="addBus = !addBus" x-show="! addBus">
<i class="fa fa-arrow-circle-right"></i> Preise für Busanreise anzeigen
</a>
</f:if>
<f:if condition="{hasDiscount}">
<a href="#" x-on:click.prevent="discountBus = !discountBus" x-show="discountBus">
<i class="fa fa-arrow-circle-right"></i> Preise für Busanreise anzeigen
</a>
<a href="#" x-on:click.prevent="discountBus = !discountBus" x-show="! discountBus">
<i class="fa fa-arrow-circle-right"></i> Preise für Eigenanreise anzeigen
</a>
</f:if>
</div>
</f:if>
<div class="table-responsive">
<table class="table table-striped dates-table">
<thead>
<tr>
<th>Termin</th>
<th>
<span data-microtip-position="bottom" aria-label="Nächte" role="tooltip">
<i class="fa fa-bed"></i>
</span>
</th>
<th>Preis ab</th>
<th>inkl.</th>
<th class="hidden-xs"></th>
</tr>
</thead>
<tbody>
<f:for each="{rows}" as="row" iteration="iteration">
<tr{f:if(condition: '{iteration.index} > 4', then: ' x-bind:class="{ \'hidden\': !showAll }"')}>
<td class="dates-table__cell dates-table__cell--date">
<f:if condition="{bookable} && {row.available} && {row.showBookingButton}">
<f:then>
<button class="dates-table__date"
x-on:click.prevent="loadPrices({row.dateUid})">
{row.dateStart} - {row.dateEnd}
</button>
<div class="hidden-md hidden-lg dates-table__links">
<button x-on:click.prevent="loadPrices({row.dateUid})" class="button button--small">
Details
</button>
<a href="{row.bookingUrl}" target="_blank" class="button button--small button--action">
Buchen
</a>
</div>
</f:then>
<f:else>
{row.dateStart} - {row.dateEnd}
</f:else>
</f:if>
</td>
<td class="dates-table__cell dates-table__cell--nights">
{row.dateNights}
</td>
<td class="dates-table__cell dates-table__cell--price">
<f:if condition="{row.datePseudoPrice}">
<span class="dates-table__pseudo-price">
{row.datePseudoPrice} €
</span>
</f:if>
<strong x-text="calculatePrice({row.dateMinPrice}, {row.dateBusPrice}, {row.dateDiscount}) + '€'"></strong>
</td>
<td class="dates-table__cell dates-table__cell--icons">
<div class="icon-wrapper hidden-xs hidden-sm">
<span x-show="showBusIcon({row.dateBusPrice}, {row.dateBusIncluded})" data-microtip-position="top" aria-label="Busfahrt inklusive" role="tooltip">
<i class="fa fa-bus"></i>
</span>
<span x-show="{row.dateSkipassIncluded}" data-microtip-position="top" aria-label="{f:if(condition: '{row.season} =="s"', then: 'Bergbahnticket inklusive', else: 'Skipass inklusive')}" role="tooltip">
<i class="fa fa-tag"></i>
</div>
</div>
<button type="button" class="button button--light button--small hidden-xs"
data-toggle="modal"
data-microtip-position="top" aria-label="Alle Inklusivleistungen anzeigen" role="tooltip">Leistungen</button>
<a href="#" data-toggle="modal"
class="hidden-sm hidden-md hidden-lg"><i class="fa fa-info-circle"></i></a>
</td>
<td class="dates-table__cell dates-table__cell--buttons hidden-xs">
<f:if condition="{row.available} || {row.isHideBookingButton}">
<button class="button button--small" x-on:click="loadPrices({row.dateUid})">
Details
</button>
</f:if>
<f:if condition="{row.available} && {row.showBookingButton}">
<f:link.typolink parameter="{row.bookingUrl}" target="_blank" class="button button--small button--action">
Buchen
</f:link.typolink>
</f:if>
<f:if condition="{row.isHideBookingButton}">
<f:link.typolink parameter="{travelAlertUrl}" class="button button--small button--mute">
Reisen-Alert
</f:link.typolink>
</f:if>
<f:if condition="{row.unavailable} && {row.showBookingButton}">
<span class="label label-default">ausgebucht</span>
</f:if>
</td>
</tr>
</f:for>
</tbody>
</table>
</div>
<f:if condition="{rowCount} > 5 || {altProductLink}">
<div class="row dates-table__links">
<div class="col-xs-12 col-md-6">
<f:if condition="{rows -> f:count()} > 5">
<a href="#" x-on:click.prevent="showAll = !showAll">
<i class="fa" x-bind:class="[{ 'fa-minus-circle': showAll }, { 'fa-plus-circle': !showAll }]"></i>
<template v-show="showAll">Weniger Termine anzeigen</template>
<template v-show="!showAll">Alle Termine anzeigen</template>
</a>
</f:if>
</div>
<f:if condition="{altProductLink}">
<div class="col-xs-12 col-md-6">
<f:link.typolink parameter="{altProductLink}" class="dates-table__altlink">
<i class="fa fa-arrow-circle-right"></i> {altLabel}
</f:link.typolink>
</div>
</f:if>
</div>
</f:if>
<!-- ToDo: Implement modals -->
<template v-for="(row, index) of datesRows">
<div class="modal fade" :id="'dateservices' + index" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
<h5 class="modal-title">Inklusivleistungen</h5>
</div>
<div class="modal-body">
<div class="table-responsive">
<services-included :services="row.dateServicesIncluded" class="services"></services-included>
</div>
</div>
</div>
</div>
</div>
</template>
</div>
</f:if>
</f:section>
</html>
@@ -1,146 +0,0 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers"
xmlns:ep="http://typo3.org/ns/EP/EpTheme/ViewHelpers">
<f:layout name="Default"/>
<f:section name="Main">
<div class="price-table-wrapper" x-data="datesTable">
<div class="row ep-headbar">
<div class="col-xs-6">Buchungsoptionen</div>
<div class="col-xs-6 text-right">{dateStart} - {dateEnd}</div>
</div>
<f:if condition="{hasSurcharge} || {hasDiscount}">
<div class="dates-table__pricetoggle">
<f:if condition="{hasSurcharge}">
<a href="#" x-on:click.prevent="addBus = !addBus" x-show="addBus">
<i class="fa fa-car"></i> Preise für Eigenanreise anzeigen
</a>
<a href="#" x-on:click.prevent="addBus = !addBus" x-show="! addBus">
<i class="fa fa-bus"></i> Preise für Busanreise anzeigen
</a>
</f:if>
<f:if condition="{hasDiscount}">
<a href="#" @click.prevent="discountBus = !discountBus" x-show="discountBus">
<i class="fa fa-bus"></i> Preise für Busanreise anzeigen
</a>
<a href="#" @click.prevent="discountBus = !discountBus" x-show="! discountBus">
<i class="fa fa-car"></i> Preise für Eigenanreise anzeigen
</a>
</f:if>
</div>
</f:if>
<div class="table-responsive">
<table class="table table-striped pricetable">
<thead>
<tr>
<th>Zimmertyp</th>
<th>Preis</th>
<th>inkl.</th>
<th></th>
<th class="hidden-xs hidden-md"></th>
</tr>
</thead>
<tbody>
<f:for each="{rows}" as="row" iteration="iteration">
<tr>
<td style="white-space: normal">
<f:if condition="{row.showBookingButton} && {row.roomAvailable}">
<f:then>
<a class="pricetable__room-label" href="{row.bookingUrl}" target="_blank">{row.roomName}</a>
<div class="hidden-lg">
<a class="button button--small button--action" href="{row.bookingUrl}" target="_blank">Jetzt buchen</a>
</div>
</f:then>
<f:else>
{row.roomName}
</f:else>
</f:if>
<f:if condition="{row.roomAvailable}">
<f:else>
<div class="hidden-lg">
<span class="label label-default">ausgebucht</span>
</div>
</f:else>
</f:if>
</td>
<td style="white-space: nowrap{f:if(condition: '{row.roomAvailable} && {row.showBookingButton}', else: ' line-through')}"
x-text="calculatePrice({row.roomPrice}, {row.roomBusPrice}, {row.roomDiscount}) + '€'"></td>
<td>
<span x-show="showBusIcon({row.roomBusPrice}, {row.roomBusIncluded})" data-microtip-position="top" aria-label="Busfahrt inklusive" role="tooltip">
<i class="fa fa-bus"></i>
</span>
<f:if condition="{row.dateSkipassIncluded}">
<span data-microtip-position="top" aria-label="{f:if(condition: '{row.season} == \'s\'', then: 'Bergbahnticket inklusive', else: 'Skipass inklusive')}" role="tooltip">
<i class="fa fa-tag"></i>
</span>
</f:if>
</td>
<td>
<f:if condition="{row.roomAvailable} && {row.roomOptionalServices -> f:count()} > 0">
<button class="button button--light button--small hidden-xs"
data-toggle="modal" data-target="'#optservices' + index"
data-microtip-position="top" aria-label="Alle Zusatzleistungen anzeigen" role="tooltip">Optionen</button>
<a href="#" class="hidden-sm hidden-md hidden-lg"
data-toggle="modal" data-target="'#optservices' + index"><i class="fa fa-info-circle"></i></a>
</f:if>
<!-- ToDo: Implement modals -->
<div class="modal fade" id="'optservices' + index" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
<h5 class="modal-title">Optionale Zusatzleistungen</h5>
</div>
<div class="modal-body">
<div class="table-responsive">
<services-optional services="row.roomOptionalServices" class="services"></services-optional>
</div>
</div>
</div>
</div>
</div>
</td>
<td class="hidden-xs hidden-md">
<f:if condition="{row.roomAvailable} && {row.showBookingButton}">
<a class="button button--small button--action"
href="{row.bookingUrl}" target="_blank">Buchen</a>
</f:if>
<f:if condition="{row.isHideBookingButton}">
<a class="button button--small button--mute" v-if="row.isHideBookingButton"
href="{travelAlertUrl}">Reisen-Alert</a>
</f:if>
<f:if condition="{row.roomAvailable} || {row.isHideBookingButton}">
<f:else>
<span class="label label-default">ausgebucht</span>
</f:else>
</f:if>
</td>
</tr>
</f:for>
</tbody>
</table>
</div>
<f:if condition="{rows -> f:count()} > 1">
<p>
<button class="button button--small" x-on:click.prevent="tableMode = 'dates'">
<i class="fa fa-undo"></i> zurück zur Terminübersicht
</button>
</p>
</f:if>
<div class="row">
<div class="col-md-12">
<div class="ep-headbar">Inklusivleistungen</div>
<div class="ep-box">
<ul class="list-unstyled">
<f:for each="{servicesIncluded}" as="service">
<li><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {service}</li>
</f:for>
</ul>
</div>
</div>
</div>
</div>
</f:section>
</html>
@@ -11,8 +11,6 @@
<f:render partial="Product/HeaderDetail" arguments="{_all}"/>
<div class="container" id="detail" x-data='productDetail({
dates: "<f:format.raw>{ep:uri.ajax(action: 'dates', controller: 'AjaxDate', arguments: '{product: product, hotel: hotel}', format: 'json', pageUid: settings.defaultAjaxUid)}</f:format.raw>",
contingents: "<f:format.raw>{ep:uri.ajax(action: 'list', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}</f:format.raw>",
rooms: "<f:format.raw>{ep:uri.ajax(action: 'rooms', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}</f:format.raw>",
pricetable: "<f:format.raw>{ep:uri.ajax(action: 'pricetable', controller: 'AjaxTable', arguments: '{product: product, hotel: hotel}', format: 'html', pageUid: settings.defaultAjaxUid)}</f:format.raw>",
travelAlert: "<f:format.raw>{f:uri.typolink(parameter: settings.travelAlertPageUid)}</f:format.raw>"
})'>
@@ -133,7 +131,7 @@
</div>
<div id="main" class="col-md-8 col-md-pull-4">
<!-- Loader -->
<div x-show="loading" class="flex space-x-2 mb-8">
<div x-show="loading" class="flex items-center space-x-2 mb-8">
<f:image class="block w-4 h-4" src="EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif" alt="Loading"/>
<span>Loading...</span>
</div>
@@ -153,17 +151,7 @@
</f:if>
<f:if condition="{product.daytrip}">
<f:then>
<f:comment><!--
<date-select
hotel-name="{hotel.name}"
:hotel-uid="{hotel.uid}"
:product-uid="{product.uid}"
:contingent-endpoint-uri="uris.contingents"
:rooms-endpoint-uri="uris.rooms"
:argument-prefix="argumentPrefix"
loader-uri="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif')}"
></date-select>
--></f:comment>
<f:render section="DaytripDateSelect" arguments="{_all}"/>
</f:then>
<f:else>
<div x-show="tableMode === 'dates'">
@@ -250,9 +238,8 @@
</f:section>
<f:section name="DatesTable">
<div class="dates-table-wrapper">
<div x-show="bookable" class="ep-headbar">Buchungsoptionen {hotel.name}</div>
<div x-show="!bookable" class="ep-headbar">Termine &amp; Leistungen</div>
<div class="dates-table-wrapper" x-show="bookable">
<div class="ep-headbar">Buchungsoptionen {hotel.name}</div>
<f:render section="PriceToggle"/>
<div class="table-responsive">
<table class="table table-striped dates-table">
@@ -304,10 +291,10 @@
<i class="fa fa-tag"></i>
</span>
</span>
<button type="button" class="button button--light button--small hidden-xs"
data-toggle="modal" data-target="'#dateservices' + index"
<button class="button button--light button--small hidden-xs"
x-on:click.prevent="$store.show.modal = 'services-' + row.dateUid"
data-microtip-position="top" aria-label="Alle Inklusivleistungen anzeigen" role="tooltip">Leistungen</button>
<a href="#" data-toggle="modal" data-target="'#dateservices' + index"
<a href="#" x-on:click.prevent="$store.show.modal = 'services-' + row.dateUid"
class="hidden-sm hidden-md hidden-lg"><i class="fa fa-info-circle"></i></a>
</td>
<td class="dates-table__cell dates-table__cell--buttons hidden-xs">
@@ -338,6 +325,33 @@
</template>
</div>
</template>
<template x-for="row in datesRows">
<div x-bind:id="'services-' + row.dateUid" x-show="$store.show.modal === $el.id" x-transition x-cloak class="fixed inset-0 w-full h-full z-50">
<div class="absolute inset-0 w-full h-full bg-overlay-black" x-on:click="$store.show.modal = null"></div>
<div class="absolute top-1/2 left-1/2 transform -translate-xy-1/2 bg-white p-8 shadow w-full max-w-3xl rounded">
<div class="flex justify-between border-b border-gray-200 pb-2 mb-4">
<span class="text-lg font-bold">
Inklusivleistungen
</span>
<button x-on:click.prevent="$store.show.modal = null" class="inline-block">
<svg class="w-8 h-8"><use href="#icon-close"></use></svg>
</button>
</div>
<table class="table table-striped">
<tbody>
<template x-for="service in row.dateServicesIncluded">
<tr>
<td>
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
<span x-text="service"></span>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</template>
</div>
</f:section>
@@ -385,30 +399,12 @@
</span>
</td>
<td>
<button x-show="hasOptionalServices(row) && row.roomAvailable"
<button x-show="row.roomHasOptionalServices && row.roomAvailable"
class="button button--light button--small hidden-xs"
data-toggle="modal" data-target="'#optservices' + index"
x-on:click.prevent="$store.show.modal = 'optional-services-' + row.roomUid"
data-microtip-position="top" aria-label="Alle Zusatzleistungen anzeigen" role="tooltip">Optionen</button>
<a href="#" x-show="hasOptionalServices(row) && row.roomAvailable" class="hidden-sm hidden-md hidden-lg"
data-toggle="modal" data-target="'#optservices' + index"><i class="fa fa-info-circle"></i></a>
<div class="modal fade" :id="'optservices' + index" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
<h5 class="modal-title">Optionale Zusatzleistungen</h5>
</div>
<div class="modal-body">
<div class="table-responsive">
<services-optional :services="row.roomOptionalServices" class="services"></services-optional>
</div>
</div>
</div>
</div>
</div>
<a href="#" x-show="row.roomHasOptionalServices && row.roomAvailable" class="hidden-sm hidden-md hidden-lg"
x-on:click.prevent="$store.show.modal = 'optional-services-' + row.roomUid"><i class="fa fa-info-circle"></i></a>
</td>
<td class="hidden-xs hidden-md">
<a class="button button--small button--action" x-show="row.roomAvailable && !row.isHideBookingButton"
@@ -422,6 +418,32 @@
</tbody>
</table>
</div>
<template x-for="row in pricesRows">
<div x-bind:id="'optional-services-' + row.roomUid" x-show="$store.show.modal === $el.id" x-transition x-cloak class="fixed inset-0 w-full h-full z-50">
<div class="absolute inset-0 w-full h-full bg-overlay-black z-25 flex items-center justify-center" x-on:click="$store.show.modal = null"></div>
<div class="absolute top-1/2 left-1/2 transform -translate-xy-1/2 bg-white p-8 shadow w-full max-w-3xl h-128 overflow-y-scroll rounded">
<div class="flex justify-between border-b border-gray-200 pb-2 mb-4">
<span class="text-2xl font-bold">
Optionale Zusatzleistungen
</span>
<button x-on:click.prevent="$store.show.modal = null" class="inline-block">
<svg class="w-8 h-8"><use href="#icon-close"></use></svg>
</button>
</div>
<template x-for="section in row.roomOptionalServices">
<div class="pb-2 border-t border-gray-200 first:border-0">
<div class="font-bold text-xl" x-text="section.label"></div>
<template x-for="optionalService in section.services">
<dl class="flex items-start justify-between mb-0">
<dt class="font-normal" x-text="optionalService.label + ':'"></dt>
<dd class="whitespace-no-wrap" x-text="optionalService.price + ' €'"></dd>
</dl>
</template>
</div>
</template>
</div>
</div>
</template>
<template x-if="!singleDate">
<p>
<button class="button button--small" x-on:click.prevent="tableMode = 'dates'">
@@ -471,6 +493,89 @@
</div>
</f:section>
<f:section name="DaytripDateSelect">
<div class="date-select-wrapper" x-data='daytripDateSelect({
hotelName: "{hotel.name}",
hotelUid: {hotel.uid},
productUid: {product.uid},
contingentEndpointUri: "<f:format.raw>{ep:uri.ajax(action: 'list', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}</f:format.raw>",
roomsEndpointUri: "<f:format.raw>{ep:uri.ajax(action: 'rooms', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}</f:format.raw>"
})'>
<div class="ep-headbar" x-text="headerLabel"></div>
<div class="row">
<div class="col-xs-12 col-md-6">
<div class="date-select">
<div class="date-select__picker">
<div class="daterange__panel">
<input x-ref="field" type="hidden">
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6">
<table class="table table-striped pricetable" x-show="availableRooms.length">
<thead>
<tr>
<th>Zimmerart</th>
<th>Preis</th>
<th>inkl.</th>
<th></th>
</tr>
</thead>
<tbody>
<template x-for="(row, index) in availableRooms">
<tr>
<td x-text="row.label"></td>
<td style="white-space: nowrap" x-text="'ab ' + calculatePrice(row.code) + ' €'"></td>
<td>
<i x-show="row.skipassIncluded" aria-label="Skipass inklusive" role="tooltip" data-microtip-position="top" class="fa fa-tag"></i>
<a href="#" data-toggle="modal"
data-target="'#services' + index"
data-microtip-position="top" aria-label="Inklusivleistungen zeigen" role="tooltip">
<i class="fa fa-info-circle"></i>
</a>
<div class="modal fade" id="'services' + index" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
<h5 class="modal-title">Inklusivleistungen</h5>
</div>
<div class="modal-body">
<div class="table-responsive">
<table class="table table-striped">
<tbody>
<tr v-for="service of row.servicesIncluded">
<td><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> {{ service }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</td>
<td>
<a x-bind:href="row.bookingUrl" target="_blank"
class="button button--small button--action">Buchen</a>
</td>
</tr>
</template>
</tbody>
</table>
<div x-show="loading" class="loader flex items-center space-x-2">
<span>Loading...</span>
<f:image class="block w-4 h-4" src="EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif" alt="Loading"/>
</div>
<div x-show="message" x-text="message"></div>
</div>
</div>
</div>
</f:section>
<f:section name="Calendar">
<f:if condition="{product.calendarHotel}">
<f:if condition="{hotel.groupsPriceConfigs}">
+4
View File
@@ -64,6 +64,7 @@ module.exports = {
extend: {
colors: {
'overlay': {
'black': tinycolor('#000000').setAlpha(0.85).toRgbString(),
'white': tinycolor('#ffffff').setAlpha(0.85).toRgbString(),
},
'ep': {
@@ -92,6 +93,9 @@ module.exports = {
'primary-80': tinycolor('#b5e400').setAlpha(0.8).toRgbString(),
},
},
height: {
'128': '32rem',
},
inset: {
'100': '100%',
'1/2': '50%',