Implement daily bookable dates
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
<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></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="entry of availableRooms">
|
||||
<td>{{entry.label}}</td>
|
||||
<td>ab {{entry.price * selectedNumberOfDays}} €</td>
|
||||
<td>
|
||||
<a :href="entry.bookingUrl" target="_blank"
|
||||
class="button button--small button--action">Buchen</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-show="loading" class="loader"><p>Loading...</p></div>
|
||||
<div v-show="message">{{message}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery'
|
||||
import Vue from 'vue'
|
||||
import flatpickr from 'flatpickr'
|
||||
import { German } from 'flatpickr/dist/l10n/de'
|
||||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/de'
|
||||
import DatesTable from './DatesTable.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DatesTable
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
picker: null,
|
||||
displayedFrom: dayjs().startOf('month'),
|
||||
displayedTo: dayjs().endOf('month'),
|
||||
selectedFrom: null,
|
||||
selectedTo: null,
|
||||
availableDates: [],
|
||||
availableRooms: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
hotelName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
hotelUid: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
productUid: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
minDate: {
|
||||
type: Date,
|
||||
default () {
|
||||
return new Date()
|
||||
}
|
||||
},
|
||||
maxDate: {
|
||||
type: Date,
|
||||
default () {
|
||||
return new Date(dayjs().add(1, 'year'))
|
||||
}
|
||||
},
|
||||
contingentEndpointUri: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
roomsEndpointUri: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
argumentPrefix: {
|
||||
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.selectedNumberOfDays + ' Nächte)';
|
||||
}
|
||||
return label;
|
||||
},
|
||||
selectedNumberOfDays () {
|
||||
if (this.selectedFrom === null || this.selectedTo === null) {
|
||||
return 0;
|
||||
}
|
||||
return this.selectedTo.diff(this.selectedFrom, 'days');
|
||||
},
|
||||
message () {
|
||||
if (this.loading) {
|
||||
return '';
|
||||
}
|
||||
if (this.selectedFrom === null || this.selectedTo === null) {
|
||||
return 'Bitte einen Zeitraum auswählen.';
|
||||
}
|
||||
if (this.selectedTo.diff(this.selectedFrom, 'days') < 2) {
|
||||
return 'Bitte mindestens zwei 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;
|
||||
$.post(this.contingentEndpointUri, query, (json) => {
|
||||
this.loading = false;
|
||||
this.availableDates = json;
|
||||
const enabledDates = [];
|
||||
for (let entry of this.availableDates) {
|
||||
enabledDates.push(entry.date);
|
||||
}
|
||||
this.picker.set('enable', enabledDates);
|
||||
}, 'json');
|
||||
},
|
||||
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;
|
||||
$.post(this.roomsEndpointUri, query, (json) => {
|
||||
this.loading = false;
|
||||
this.availableRooms = json;
|
||||
}, 'json');
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.picker = $(this.$refs.field).flatpickr({
|
||||
mode: 'range',
|
||||
dateFormat: 'Y-m-d',
|
||||
locale: German,
|
||||
inline: true,
|
||||
minDate: new Date(this.minDate),
|
||||
maxDate: new Date(this.maxDate),
|
||||
onChange: (selectedDates) => {
|
||||
this.availableRooms = [];
|
||||
if (selectedDates.length === 2) {
|
||||
this.selectedFrom = dayjs(selectedDates[0]);
|
||||
this.selectedTo = dayjs(selectedDates[1]);
|
||||
if (this.selectedTo.diff(this.selectedFrom, 'days') > 1) {
|
||||
this.fetchAvailableRooms();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Vue.nextTick(() => {
|
||||
this.fetchEnabledDates();
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="dates-table-wrapper">
|
||||
<div v-if="bookable" class="ep-headbar">{{ headerLabel }}</div>
|
||||
<div v-if="!bookable" class="ep-headbar">Termine & Leistungen</div>
|
||||
<template v-if="showHeader">
|
||||
<div v-if="bookable" class="ep-headbar">{{ headerLabel }}</div>
|
||||
<div v-if="!bookable" class="ep-headbar">Termine & Leistungen</div>
|
||||
</template>
|
||||
<div class="dates-table__pricetoggle" v-if="hasSurcharge || hasDiscount">
|
||||
<a href="#" v-if="hasSurcharge" v-on:click.prevent="toggleBus">
|
||||
<template v-if="addBus">
|
||||
@@ -127,6 +129,10 @@
|
||||
altLabel: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="price-table-wrapper">
|
||||
<div class="row ep-headbar">
|
||||
<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>
|
||||
@@ -130,6 +130,10 @@
|
||||
singleDate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showHeader: {
|
||||
typoe: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
||||
+66
-3
@@ -6,6 +6,7 @@
|
||||
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 AjaxContent from './AjaxContent.vue'
|
||||
import Calendar from './Calendar.vue'
|
||||
@@ -21,6 +22,7 @@
|
||||
PriceTable,
|
||||
ServicesIncluded,
|
||||
ServicesOptional,
|
||||
DateSelect,
|
||||
DaterangeSelect,
|
||||
AjaxContent,
|
||||
Calendar,
|
||||
@@ -47,7 +49,42 @@
|
||||
singleDate: false
|
||||
}
|
||||
},
|
||||
props: [ 'filterSettings', 'argumentPrefix', 'uris', 'hotelFirst', 'fbPixelData', 'watchlist' ],
|
||||
props: {
|
||||
filterSettings: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
argumentPrefix: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
uris: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
hotelFirst: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fbPixelData: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
watchlist: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
daytrip: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dateLabel () {
|
||||
return this.dateStart + ' - ' + this.dateEnd;
|
||||
@@ -79,10 +116,36 @@
|
||||
this.loading = false;
|
||||
EventBus.$emit('tableLoaded');
|
||||
}, 'json');
|
||||
|
||||
},
|
||||
loadSelectableDates () {
|
||||
this.loading = true;
|
||||
this.datesShow = true;
|
||||
this.priceTableShow = false;
|
||||
this.detailShow = false;
|
||||
let query = {};
|
||||
query[this.argumentPrefix + '[product]'] = this.filterSettings;
|
||||
$.post(this.uris['dateslist'], query, (json) => {
|
||||
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.loading = false;
|
||||
}
|
||||
}, 'json');
|
||||
},
|
||||
loadDatesView (scroll = true) {
|
||||
this.loadDates();
|
||||
if (this.daytrip) {
|
||||
this.loading = false;
|
||||
this.datesShow = true;
|
||||
} else {
|
||||
this.loadDates();
|
||||
}
|
||||
this.activeSection = 'dates';
|
||||
if (scroll) {
|
||||
this.scrollToMain();
|
||||
|
||||
@@ -13,13 +13,16 @@
|
||||
<product-detail
|
||||
:filter-settings='<v:format.json.encode>{filterSettings}</v:format.json.encode>'
|
||||
:uris='{
|
||||
dates: "<f:format.raw>{ep:uri.ajax(action: 'dates', controller: 'AjaxDate', pageUid: settings.defaultAjaxUid, arguments: '{product: product, hotel: hotel}', format: 'json')}</f:format.raw>",
|
||||
pricetable: "<f:format.raw>{ep:uri.ajax(action: 'pricetable', controller: 'AjaxTable', pageUid: settings.defaultAjaxUid, arguments: '{product: product, hotel: hotel}', format: 'json')}</f:format.raw>"
|
||||
dates: "<f:format.raw>{ep:uri.ajax(action: 'dates', controller: 'AjaxDate', arguments: '{product: product, hotel: hotel}', format: 'json', noCacheHash: 1)}</f:format.raw>",
|
||||
contingents: "<f:format.raw>{ep:uri.ajax(action: 'list', controller: 'AjaxContingent', format: 'json', noCacheHash: 1)}</f:format.raw>",
|
||||
rooms: "<f:format.raw>{ep:uri.ajax(action: 'rooms', controller: 'AjaxContingent', format: 'json', noCacheHash: 1)}</f:format.raw>",
|
||||
pricetable: "<f:format.raw>{ep:uri.ajax(action: 'pricetable', controller: 'AjaxTable', arguments: '{product: product, hotel: hotel}', format: 'json', noCacheHash: 1)}</f:format.raw>"
|
||||
}'
|
||||
argument-prefix='<ep:argumentPrefix pluginName="Ajax" />'
|
||||
:hotel-first="{f:if(condition: settings.hotelOnTop, then: 'true', else: 'false')}"
|
||||
:fb-pixel-data="{ productUid: <f:format.raw>{product.uid}</f:format.raw>, nameInternal: '<f:format.raw>{product.nameInternal}</f:format.raw>' }"
|
||||
:watchlist="watchlist"
|
||||
:daytrip="{product.daytrip -> v:variable.convert(type: 'int')}"
|
||||
inline-template>
|
||||
<div class="product-detail-wrapper">
|
||||
<div class="row">
|
||||
@@ -128,23 +131,37 @@
|
||||
</div>
|
||||
</f:if>
|
||||
</f:if>
|
||||
<template v-if="available">
|
||||
<dates-table :dates-rows="datesRows"
|
||||
:alt-product-link="altProductLink"
|
||||
:alt-label="altLabel"
|
||||
hotel-name="{hotel.name}"
|
||||
:bookable="bookable"
|
||||
></dates-table>
|
||||
</template>
|
||||
<f:if condition="{isProductWithNoInfoConcept}">
|
||||
<f:if condition="{product.daytrip}">
|
||||
<f:then>
|
||||
<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"
|
||||
></date-select>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<template v-else>
|
||||
<div class="alert alert-info">
|
||||
Die von dir gewählte Reise ist aktuell nicht buchbar, aber wir helfen dir gerne dabei,
|
||||
eine andere Reise zu finden. Ruf uns an unter <a href="tel:{settings.phoneNumber -> v:format.pregReplace(pattern: '/[\-\s]/', replacement: '')}" title="Service Telefon anrufen" rel="nofollow">{settings.phoneNumber}</a>
|
||||
oder schreibe an <f:link.email email="[email protected]"/>.
|
||||
</div>
|
||||
<template v-if="available">
|
||||
<dates-table :dates-rows="datesRows"
|
||||
:alt-product-link="altProductLink"
|
||||
:alt-label="altLabel"
|
||||
hotel-name="{hotel.name}"
|
||||
:bookable="bookable"
|
||||
></dates-table>
|
||||
</template>
|
||||
<f:if condition="{isProductWithNoInfoConcept}">
|
||||
<f:else>
|
||||
<template v-else>
|
||||
<div class="alert alert-info">
|
||||
Die von dir gewählte Reise ist aktuell nicht buchbar, aber wir helfen dir gerne dabei,
|
||||
eine andere Reise zu finden. Ruf uns an unter <a href="tel:{settings.phoneNumber -> v:format.pregReplace(pattern: '/[\-\s]/', replacement: '')}" title="Service Telefon anrufen" rel="nofollow">{settings.phoneNumber}</a>
|
||||
oder schreibe an <f:link.email email="[email protected]"/>.
|
||||
</div>
|
||||
</template>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</f:else>
|
||||
</f:if>
|
||||
<f:render partial="Product/Details" arguments="{_all}"/>
|
||||
@@ -152,7 +169,8 @@
|
||||
<!-- /Dates -->
|
||||
<!-- Prices -->
|
||||
<div v-cloak v-show="!loading && priceTableShow">
|
||||
<price-table :price-table-rows="priceTableRows" :services-included="servicesIncluded" :date-label="dateLabel" :single-date="singleDate"></price-table>
|
||||
<price-table :price-table-rows="priceTableRows" :services-included="servicesIncluded"
|
||||
:date-label="dateLabel" :single-date="singleDate"></price-table>
|
||||
<f:render partial="Product/Details" arguments="{_all}"/>
|
||||
</div>
|
||||
<!-- /Prices -->
|
||||
|
||||
Reference in New Issue
Block a user