WIP Migrate to stimulus.js and tailwind

This commit is contained in:
Björn Fromme
2021-08-21 13:13:37 +02:00
parent e4c4a5253e
commit e84130f565
25 changed files with 387 additions and 432 deletions
@@ -71,19 +71,6 @@ class AjaxContingentController extends ActionController
}
}
/**
* @param Hotel $hotel
* @param Product $product
* @return string
* @throws \Doctrine\DBAL\DBALException
*/
public function listAction(Hotel $hotel, Product $product)
{
$enabled = $this->contingentDataService->getAvailableContingents($hotel, $product);
return \json_encode($enabled);
}
/**
* @param Hotel $hotel
* @param Product $product
@@ -31,6 +31,7 @@ use EP\EpProducts\Domain\Model\Date;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Domain\Repository\ProductRepository;
use EP\EpProducts\Service\ContingentDataService;
use EP\EpProducts\Service\DateService;
use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\ResellerDataService;
@@ -65,22 +66,31 @@ class AjaxTableController extends ActionController
protected $resellerDataService;
/**
* @var ContingentDataService
*/
protected $contingentDataService;
/**
* @param ProductRepository $productRepository
* @param DateService $dateService
* @param FilterService $filterService
* @param ResellerDataService $resellerDataService
* @param ContingentDataService $contingentDataService
*/
public function __construct
(
ProductRepository $productRepository,
DateService $dateService,
FilterService $filterService,
ResellerDataService $resellerDataService
ResellerDataService $resellerDataService,
ContingentDataService $contingentDataService
) {
parent::__construct();
$this->productRepository = $productRepository;
$this->dateService = $dateService;
$this->filterService = $filterService;
$this->resellerDataService = $resellerDataService;
$this->contingentDataService = $contingentDataService;
}
public function initializeDatesAction()
@@ -252,4 +262,20 @@ class AjaxTableController extends ActionController
]);
}
/**
* @param Hotel $hotel
* @param Product $product
* @param string $dateFrom
* @param string $dateTo
*/
public function daytripRoomsAction(Hotel $hotel, Product $product, $dateFrom, $dateTo)
{
$template = $this->settings['bpnBookingUrlTemplateCode'];
$rooms = $this
->contingentDataService
->getAvailableRooms($dateFrom, $dateTo, $hotel, $product, $template);
$this->view->assign('rooms', $rooms);
}
}
@@ -31,6 +31,7 @@ use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Domain\Repository\HotelRepository;
use EP\EpProducts\Domain\Repository\ProductRepository;
use EP\EpProducts\Service\ContingentDataService;
use EP\EpProducts\Service\DateService;
use EP\EpProducts\Service\FilterService;
use EP\EpProducts\Service\HotelDataService;
@@ -70,12 +71,17 @@ class ProductController extends ActionController
* @var HotelDataService
*/
protected $hotelDataService;
/**
* @var ContingentDataService
*/
private $contingentDataService;
/**
* @param ProductRepository $productRepository
* @param ProductDataService $productDataService
* @param HotelRepository $hotelRepository
* @param HotelDataService $hotelDataService
* @param ContingentDataService $contingentDataService
* @param DateService $dateService
* @param FilterService $filterService
*/
@@ -84,6 +90,7 @@ class ProductController extends ActionController
ProductDataService $productDataService,
HotelRepository $hotelRepository,
HotelDataService $hotelDataService,
ContingentDataService $contingentDataService,
DateService $dateService,
FilterService $filterService
) {
@@ -91,8 +98,9 @@ class ProductController extends ActionController
$this->productRepository = $productRepository;
$this->productDataService = $productDataService;
$this->hotelRepository = $hotelRepository;
$this->dateService = $dateService;
$this->hotelDataService = $hotelDataService;
$this->contingentDataService = $contingentDataService;
$this->dateService = $dateService;
$this->filterService = $filterService;
}
@@ -146,6 +154,14 @@ class ProductController extends ActionController
}
}
if ($product->isDaytrip()) {
$availableContingents = $this
->contingentDataService
->getAvailableContingents($hotel, $product)
;
$enabledDates = array_keys($availableContingents);
}
$excludedConceptUids = GeneralUtility::trimExplode(',', $this->settings['excludedConceptUids']);
$isProductWithExcludedConcept = ($product->getConcept() !== null) && \in_array($product->getConcept()->getUid(),
$excludedConceptUids, false);
@@ -157,29 +173,10 @@ class ProductController extends ActionController
$backlinkUrl = GeneralUtility::_GET('ref');
$backLinkLabel = strpos($backlinkUrl, 'suche') !== -1 ? 'zurück zum Suchergebnis' : 'zurück';
// Generate dates uri
$datesArguments = [
'dateFrom' => $dateFrom,
'dateTo' => $dateTo,
'product' => $product,
'hotel' => $hotel
];
$datesUri = $this->uriBuilder
->reset()
->setCreateAbsoluteUri(true)
->setTargetPageType(1702)
->setTargetPageUid(855)
->uriFor(
'dates',
$datesArguments,
'AjaxDate',
'EpProducts',
'ajax'
);
$this->view->assignMultiple([
'hotel' => $hotel,
'product' => $product,
'enabledDates' => $enabledDates ?? [],
'isProductWithExcludedConcept' => $isProductWithExcludedConcept,
'isProductWithNoInfoConcept' => $isProductWithNoInfoConcept,
'currentPageUid' => $GLOBALS['TSFE']->id,
@@ -187,7 +184,6 @@ class ProductController extends ActionController
'dateTo' => $dateTo,
'referringUrl' => $backlinkUrl,
'backlinkLabel' => $backLinkLabel,
'datesUri' => $datesUri,
]);
}
@@ -140,14 +140,14 @@ class ContingentRepository extends AbstractRepository implements ProviderInterfa
}
/**
* @param \DateTime $dateFrom
* @param \DateTime $dateTo
* @param string $dateFrom
* @param string $dateTo
* @param Hotel $hotel
* @param Product $product
* @return array
* @throws DBALException
*/
public function getAvailableRooms(\DateTime $dateFrom, \DateTime $dateTo, Hotel $hotel, Product $product)
public function getAvailableRooms(string $dateFrom, string $dateTo, Hotel $hotel, Product $product)
{
$qb = $this->getDbConnection()->createQueryBuilder();
@@ -157,7 +157,7 @@ class ContingentRepository extends AbstractRepository implements ProviderInterfa
'c.additional_night_min_price as additionalNightPrice',
'c.additional_night_min_nights as additionalNightMinNights', 'd.bus_pro_id as busProId',
'd.services_included as servicesIncluded', 'd.services_general as servicesOptional',
'd.skipass_included as skipassIncluded')
'd.skipass_included as skipassIncluded', 'd.season as season')
->from('tx_epproducts_domain_model_contingent', 'c')
->innerJoin('c', 'tx_epproducts_domain_model_date', 'd', 'c.date = d.date_start AND c.hotel_code = d.hotel_code')
->where('c.hotel = :hotelUid')
@@ -168,8 +168,8 @@ class ContingentRepository extends AbstractRepository implements ProviderInterfa
->setParameters([
'hotelUid' => $hotel->getUid(),
'productUid' => $product->getUid(),
'dateFrom' => $dateFrom->format('Y-m-d'),
'dateTo' => $dateTo->format('Y-m-d'),
'dateFrom' => $dateFrom,
'dateTo' => $dateTo,
])
->groupBy('c.room_code')
->orderBy('c.pax')
@@ -31,6 +31,7 @@ use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Model\Product;
use EP\EpProducts\Domain\Repository\ContingentRepository;
use EP\EpProducts\Utility\BookingUrlUtility;
use League\Period\Period;
class ContingentDataService
{
@@ -82,27 +83,32 @@ class ContingentDataService
* @throws \Doctrine\DBAL\DBALException
*/
public function getAvailableRooms(
\DateTime $dateFrom,
\DateTime $dateTo,
string $dateFrom,
string $dateTo,
Hotel $hotel,
Product $product,
$template = 'base'
) {
$data = $this->contingentRepository->getAvailableRooms($dateFrom, $dateTo, $hotel, $product);
$period = new Period($dateFrom, $dateTo);
$nights = $period->getDateInterval()->format('%a');
$rooms = [];
foreach ($data as $row)
{
foreach ($data as $row) {
$row['bookingUrl'] = BookingUrlUtility::generateUrl([
'bookingId' => $row['busProId'],
'hotelId' => $row['hotelBusProId'],
'dateEnd' => $dateTo->format('Y-m-d'),
'dateEnd' => $dateTo,
'template' => $template,
]);
$row['servicesIncluded'] = unserialize($row['servicesIncluded']);
$row['servicesOptional'] = unserialize($row['servicesOptional']);
$row['summer'] = $row['season'] === 's';
$row['servicesIncluded'] = unserialize($row['servicesIncluded'], ['allowed_classes' => false]);
$row['servicesOptional'] = unserialize($row['servicesOptional'], ['allowed_classes' => false]);
$row['priceForSelection'] = $row['price'] + ($nights - $row['minNights']) * $row['additionalNightPrice'];
$rooms[] = $row;
}
@@ -161,6 +161,7 @@ tx_epproducts_ajax_json {
2 = pricetable
3 = pricetableHtml
4 = eventPricetable
5 = daytripRooms
}
AjaxCalendar {
1 = range
@@ -366,7 +366,7 @@ $iconRegistry->registerIcon(
'AjaxSearch' => 'searchresult',
'AjaxDate' => 'dates',
'AjaxContingent' => 'list,rooms',
'AjaxTable' => 'datestable,pricetable,pricetableHtml,eventPricetable',
'AjaxTable' => 'datestable,pricetable,pricetableHtml,eventPricetable,daytripRooms',
'AjaxCalendar' => 'range,contingents,availableRooms',
'AjaxWatchlist' => 'list',
'AjaxGroupsPrice' => 'processForm',
@@ -10,12 +10,8 @@ smoothscroll.polyfill()
// Alpine stuff
import Alpine from 'alpinejs'
import daytripDateSelect from './components/daytrip-date-select'
import searchBar from './components/search-bar'
import contactForm from './components/contact-form'
Alpine.data('daytripDateSelect', daytripDateSelect)
Alpine.data('searchBar', searchBar)
Alpine.data('contactForm', contactForm)
Alpine.start()
@@ -1,153 +0,0 @@
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');
}
})
@@ -1,143 +0,0 @@
import axios from 'axios'
import flatpickr from 'flatpickr'
import { German } from 'flatpickr/dist/l10n/de'
export default props => ({
loading: false,
picker: null,
uri: props.uri,
sticky: props.sticky || false,
searchParams: props.initialSearchParams,
filterOptions: props.initialFilterOptions,
datePresets: props.datePresets,
show: null,
destinationName: '',
conceptSelected: false,
destinationSelected: false,
dateRangeSelected: { from: null, to: null },
dateRangeLabel: '',
init() {
this.$watch('dateRangeSelected', () => {
if (null === this.dateRangeSelected.from && null === this.dateRangeSelected.to) {
this.searchParams.dateFrom = null
this.searchParams.dateTo = null
} else {
let dateFrom = this.dateRangeSelected.from
let dateTo = this.dateRangeSelected.to
this.searchParams.dateFrom = flatpickr.formatDate(dateFrom, 'Y-m-d')
this.searchParams.dateTo = flatpickr.formatDate(dateTo, 'Y-m-d')
}
this.updateDateRangeLabel()
this.load()
})
},
load() {
this.loading = true
const data = new FormData()
data.set('tx_epproducts_ajax[searchParams]', JSON.stringify(this.searchParams))
axios.post(this.uri, data)
.then(response => {
this.applyOptions(response.data)
this.show = null
})
.finally(() => {
this.loading = false
})
},
applyOptions(json) {
this.filterOptions.totalResults = json.totalResults
const filterOptions = json.filterOptions
this.filterOptions = {
...this.filterOptions,
totalResults: json.totalResults,
priceRanges: filterOptions.priceRanges,
pax: filterOptions.pax,
destinations: filterOptions.destinations
}
if (filterOptions.dateFrom !== null) {
this.filterOptions.selectableRangeFrom = filterOptions.dateFrom
if (this.picker !== null) {
this.picker.set('minDate', new Date(filterOptions.dateFrom))
}
}
if (filterOptions.dateTo !== null) {
this.filterOptions.selectableRangeTo = filterOptions.dateTo
if (this.picker !== null) {
this.picker.set('maxDate', new Date(filterOptions.dateTo))
}
}
},
updateSearchParam(param, value) {
this.searchParams = {...this.searchParams, [param]: value}
this.load()
},
selectDateRangePreset(preset) {
let dateFrom = new Date(preset.dateFrom)
let dateTo = new Date(preset.dateTo)
this.dateRangeSelected = { from: dateFrom, to: dateTo }
this.picker.setDate([ dateFrom, dateTo ])
},
resetDateRange () {
this.dateRangeSelected = { from: null, to: null }
this.picker.clear()
this.picker.jumpToDate()
},
selectDestination(uid, name, type) {
this.destinationName = name
this.destinationSelected = type !== 'concept'
this.conceptSelected = type === 'concept'
this.searchParams = {...this.searchParams, destinationUid: uid, destinationType: type}
this.load()
},
resetDestination() {
this.destinationName = ''
this.destinationSelected = false
this.conceptSelected = false
this.searchParams = {...this.searchParams, destinationUid: null, destinationType: null}
this.load()
},
initDatePicker() {
this.picker = flatpickr(this.$refs.picker, {
mode: 'range',
locale: German,
inline: true,
onChange: selectedDates => {
if (selectedDates.length === 2) {
this.dateRangeSelected = { from: selectedDates[0], to: selectedDates[1] }
}
}
})
let minDate = this.filterOptions.dateFrom ? new Date(this.filterOptions.dateFrom) : new Date
this.picker.set('minDate', minDate)
if (this.filterOptions.dateTo) {
this.picker.set('maxDate', new Date(this.filterOptions.dateTo))
}
},
priceRangeLabel() {
if (null === this.searchParams.priceRange) {
return ''
}
let range = this.filterOptions.priceRanges[this.searchParams.priceRange]
return this.priceRangeFormatted(range)
},
priceRangeFormatted(range) {
let min = range.min;
let max = range.max;
if (min === 1) {
return 'bis ' + max + ' €'
}
if (max === 999) {
return 'ab ' + min + ' €'
}
return min + ' - ' + max + ' €'
},
updateDateRangeLabel() {
let dateRangeLabel = ''
if (null !== this.dateRangeSelected.from && null !== this.dateRangeSelected.to) {
let dateFrom = this.dateRangeSelected.from
let dateTo = this.dateRangeSelected.to
dateRangeLabel = `${flatpickr.formatDate(dateFrom, 'd.m.Y')} - ${flatpickr.formatDate(dateTo, 'd.m.Y')}`
}
this.dateRangeLabel = dateRangeLabel
},
})
@@ -15,6 +15,7 @@ export default class extends Controller {
minDate: String,
dateFrom: String,
dateTo: String,
enabledDates: Array,
}
initialize() {
@@ -33,6 +34,24 @@ export default class extends Controller {
}
}
})
if (this.hasEnabledDatesValue) {
let enabledDates = this.enabledDatesValue
let minDate = enabledDates[0]
let maxDate = enabledDates[enabledDates.length - 1]
if (this.hasMinDateValue && this.minDateValue > minDate) {
minDate = this.minDateValue
}
this.picker.set('enable', enabledDates)
this.picker.set('minDate', minDate)
this.picker.set('maxDate', maxDate)
this.picker.jumpToDate(minDate)
this.picker.set('onDayCreate', (dObj, dStr, fp, dayElem) => {
let day = flatpickr.formatDate(dayElem.dateObj, 'Y-m-d')
if (day >= minDate && -1 !== this.enabledDatesValue.indexOf(day)) {
dayElem.classList.add('available')
}
})
}
}
connect() {
@@ -0,0 +1,58 @@
import { Controller } from 'stimulus'
import { useDispatch } from 'stimulus-use'
import { useFetch } from '../mixins/use_fetch'
export default class extends Controller {
static targets = [
'headerLabel',
'container',
'loadingIndicator',
'message',
]
static values = {
loading: Boolean,
contingentsUri: String,
roomsUri: String,
hotelUid: Number,
productUid: Number,
dateFrom: String,
dateTo: String,
}
initialize() {
useDispatch(this)
useFetch(this)
}
openModal(e) {
let eventName = `${e.currentTarget.dataset.type}-modal:open`
this.dispatch(eventName, e.currentTarget.dataset.uid)
}
load(e) {
this.loadingValue = true
this.dateFromValue = e.detail.from
this.dateToValue = e.detail.to
let data = new FormData()
data.set('tx_epproducts_ajax[hotel]', this.hotelUidValue)
data.set('tx_epproducts_ajax[product]', this.productUidValue)
data.set('tx_epproducts_ajax[dateFrom]', this.dateFromValue)
data.set('tx_epproducts_ajax[dateTo]', this.dateToValue)
fetch(this.roomsUriValue, { method: 'POST', body: data })
.then(this.checkStatus)
.then(this.parseHTML)
.then(html => {
this.containerTarget.innerHTML = html
this.messageTarget.classList.add('hidden')
})
.finally(() => {
this.loadingValue = false
})
}
loadingValueChanged() {
this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false)
}
}
@@ -18,6 +18,7 @@ export default class extends Controller {
loading: Boolean,
loaded: Boolean,
mode: String,
daytrip: Boolean,
}
initialize() {
@@ -26,7 +27,7 @@ export default class extends Controller {
}
connect() {
if ('dates' === this.modeValue) {
if (false === this.dayTripValue && 'dates' === this.modeValue) {
this.loadDates()
}
}
@@ -86,7 +87,11 @@ export default class extends Controller {
}
modeValueChanged() {
this.datesTableContainerTarget.classList.toggle('hidden', this.modeValue === 'prices')
this.priceTableContainerTarget.classList.toggle('hidden', this.modeValue === 'dates')
if (this.hasDatesTableContainerTarget) {
this.datesTableContainerTarget.classList.toggle('hidden', this.modeValue === 'prices')
}
if (this.hasPriceTableContainerTarget) {
this.priceTableContainerTarget.classList.toggle('hidden', this.modeValue === 'dates')
}
}
}
@@ -24,7 +24,7 @@ export default class extends Controller {
this.dateRangeLabelTarget.value = e.detail.label
this.dateFromTarget.value = e.detail.from
this.dateToTarget.value = e.detail.to
this.dispatch('input')
this.dispatch('input', this.getDetails())
}
setPax(e) {
@@ -32,7 +32,7 @@ export default class extends Controller {
let pax = e.currentTarget.dataset.option
this.paxTarget.value = pax
this.paxLabelTarget.value = pax
this.dispatch('input')
this.dispatch('input', this.getDetails())
}
setDestination(e) {
@@ -40,7 +40,7 @@ export default class extends Controller {
this.destinationUidTarget.value = e.currentTarget.dataset.optionUid
this.destinationTypeTarget.value = e.currentTarget.dataset.optionType
this.destinationLabelTarget.value = e.currentTarget.dataset.optionLabel
this.dispatch('input')
this.dispatch('input', this.getDetails())
}
resetDestination(e) {
@@ -48,13 +48,24 @@ export default class extends Controller {
this.destinationUidTarget.value = ''
this.destinationTypeTarget.value = ''
this.destinationLabelTarget.value = ''
this.dispatch('input')
this.dispatch('input', this.getDetails())
}
setPriceRange(e) {
e.preventDefault()
this.priceRangeTarget.value = e.currentTarget.dataset.optionValue
this.priceRangeLabelTarget.value = e.currentTarget.dataset.labelValue
this.dispatch('input')
this.dispatch('input', this.getDetails())
}
getDetails() {
return {
dateFrom: this.dateFromTarget.value,
dateTo: this.dateToTarget.value,
pax: this.paxTarget.value,
destinationUid: this.destinationUidTarget.value,
destinationType: this.destinationTypeTarget.value,
priceRange: this.priceRangeTarget.value,
}
}
}
@@ -33,6 +33,7 @@ export default class extends Controller {
}
stickyValueChanged() {
this.element.classList.toggle(this.stickyClass, this.stickyValue)
let stickyClass = this.hasStickyClass ? this.stickyClass : 'sticky'
this.element.classList.toggle(stickyClass, this.stickyValue)
}
}
@@ -0,0 +1,5 @@
<html data-namespace-typo3-fluid="true" lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
</html>
@@ -0,0 +1,57 @@
<html data-namespace-typo3-fluid="true" lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:form id="contactform" object="{contactForm}" name="contactForm"
action="processForm" controller="AjaxForm" pluginName="Ajax" pageType="1803"
pageUid="{settings.defaultAjaxUid}"
data="{'contactform-target': 'form', 'action': 'contactform#submit'}">
<f:form.hidden property="pageUrl"/>
<label class="block mb-4 hidden">
<span class="text-gray-700">Betreff*</span>
<f:form.textfield class="border-gray-400 focus:border-gray-800 mt-1 w-full" property="subject"/>
</label>
<label class="block mb-4" x-bind:class="'has-validation-error' && formErrors.gender">
<span x-bind:class="formErrors.gender ? 'text-red-500' : 'text-gray-700'">Gender*</span>
<f:form.select class="border-gray-400 focus:border-gray-800 mt-1 w-full" property="gender" options="{M: 'M', W: 'W', D: 'D'}"/>
<div class="text-red-500" x-show="formErrors.gender" x-html="formErrors.gender"></div>
</label>
<label class="block mb-4" x-bind:class="'has-validation-error' && formErrors.name">
<span x-bind:class="formErrors.name ? 'text-red-500' : 'text-gray-700'">Name*</span>
<f:form.textfield class="border-gray-400 focus:border-gray-800 mt-1 w-full" property="name"/>
<div class="text-red-500" x-show="formErrors.name" x-html="formErrors.name"></div>
</label>
<label class="block mb-4" x-bind:class="'has-validation-error' && formErrors.firstName">
<span x-bind:class="formErrors.firstName ? 'text-red-500' : 'text-gray-700'">Vorname*</span>
<f:form.textfield class="border-gray-400 focus:border-gray-800 mt-1 w-full" property="firstName"/>
<div class="text-red-500" x-show="formErrors.firstName" x-html="formErrors.firstName"></div>
</label>
<label class="block mb-4" x-bind:class="'has-validation-error' && formErrors.email">
<span x-bind:class="formErrors.email ? 'text-red-500' : 'text-gray-700'">E-Mail*</span>
<f:form.textfield class="border-gray-400 focus:border-gray-800 mt-1 w-full" property="email"/>
<div class="text-red-500" x-show="formErrors.email" x-html="formErrors.email"></div>
</label>
<label class="block mb-4" x-bind:class="'has-validation-error' && formErrors.phone">
<span x-bind:class="formErrors.phone ? 'text-red-500' : 'text-gray-700'">Telefon/Fax*</span>
<f:form.textfield class="border-gray-400 focus:border-gray-800 mt-1 w-full" property="phone"/>
<div class="text-red-500" x-show="formErrors.phone" x-html="formErrors.phone"></div>
</label>
<label class="block mb-4" x-bind:class="'has-validation-error' && formErrors.inquiry">
<span x-bind:class="formErrors.inquiry ? 'text-red-500' : 'text-gray-700'">(Zusatz-) Wünsche</span>
<f:form.textarea class="border-gray-400 focus:border-gray-800 mt-1 w-full"
property="inquiry"
placeholder="z. B. Reiseleiter, Halbpension, Bus Anreise, Programm"
cols="30" rows="5"/>
<div class="text-red-500" x-show="formErrors.inquiry" x-html="formErrors.inquiry"></div>
</label>
<div class="mb-4">
Ich habe die <f:link.typolink parameter="{settings.dataProtectionPageUid}"
title="Datenschutzerklärung"
target="_blank">Datenschutzbestimmungen</f:link.typolink> gelesen und akzeptiert.
</div>
<div class="mb-4">
<button type="submit" class="button button-warning">Jetzt anfragen</button>
</div>
</f:form>
</html>
@@ -0,0 +1,11 @@
<div 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="Form/Default"/>
<f:section name="Main">
<f:render partial="ContactForm/Simple" arguments="{_all}"/>
</f:section>
</div>
@@ -0,0 +1,20 @@
<div 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="Form/Default"/>
<f:section name="Main">
<div class="form form--border"
data-controller="contactform">
<div data-contactform-target="container">
<f:render partial="ContactForm/Simple" arguments="{_all}"/>
</div>
<div class="hidden form__overlay"
data-contactform-target="loadingIndicator">
<img src="{f:uri.resource(path: 'images/ajax-loader-white.gif', extensionName: 'ep_theme')}" alt="Processing...">
</div>
</div>
</f:section>
</div>
@@ -0,0 +1,103 @@
<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">
<table class="table table-striped pricetable">
<thead>
<tr>
<th>Zimmerart</th>
<th>Preis</th>
<th>inkl.</th>
<th></th>
</tr>
</thead>
<tbody>
<f:for each="{rooms}" as="row" iteration="iteration">
<tr>
<td>
{row.label}
</td>
<td class="whitespace-nowrap">
ab {row.priceForSelection} €
</td>
<td>
<f:if condition="{row.skipassIncluded}">
<span data-microtip-position="top"
aria-label="{f:if(condition: row.summer, then: 'Bergbahnticket inklusive', else: 'Skipass inklusive')}"
role="tooltip">
<svg class="w-4 h-4">
<use href="#icon-tag"></use>
</svg>
</span>
</f:if>
<button class="button button--light button--small hidden md:block"
data-action="daytrip#openModal"
data-type="services"
data-uid="{iteration.cycle}"
data-microtip-position="top"
aria-label="Alle Inklusivleistungen anzeigen"
role="tooltip">
Leistungen
</button>
</td>
<td>
<a class="button button--small bg-green-500 border-green-500"
href="{row.bookingUrl}" target="_blank">
Buchen
</a>
</td>
</tr>
</f:for>
</tbody>
</table>
<div class="hidden fixed inset-0 w-full h-full z-50"
data-controller="modal"
data-action="daytrip:services-modal:open@window->modal#open">
<div class="absolute inset-0 w-full h-full bg-overlay-black z-25 flex items-center justify-center"
data-action="click->modal#close"></div>
<div class="absolute top-1/2 left-1/2 transform -translate-xy-1/2 bg-white shadow w-full max-w-xl rounded">
<div class="shadow flex items-center justify-between p-4 bg-ep-primary text-white rounded-t">
<span class="text-xl font-bold">
Inklusivleistungen
</span>
<button class="inline-block"
data-action="modal#close">
<svg class="w-8 h-8">
<use href="#icon-close"></use>
</svg>
</button>
</div>
<div class="p-4">
<f:for each="{rooms}" as="row" iteration="iteration">
<table class="hidden table table-striped"
data-modal-target="content"
data-uid="{iteration.cycle}">
<tbody>
<f:for each="{row.servicesIncluded}" as="service">
<tr>
<td>
<div class="flex items-center space-x-2">
<svg class="w-6 h-6 text-ep-primary">
<use href="#icon-check"></use>
</svg>
<span>
{service}
</span>
</div>
</td>
</tr>
</f:for>
</tbody>
</table>
</f:for>
</div>
</div>
</div>
</f:section>
</html>
@@ -18,7 +18,8 @@
</f:if>
data-product-details-datestable-uri-value="{ep:uri.ajax(action: 'datestable', controller: 'AjaxTable', arguments: '{product: product, hotel: hotel}', format: 'html', pageUid: settings.defaultAjaxUid)}"
data-product-details-pricetable-uri-value="{ep:uri.ajax(action: 'pricetable', controller: 'AjaxTable', arguments: '{product: product, hotel: hotel}', format: 'html', pageUid: settings.defaultAjaxUid)}"
data-product-details-travel-alert-uri-value="{f:uri.typolink(parameter: settings.travelAlertPageUid)}">
data-product-details-travel-alert-uri-value="{f:uri.typolink(parameter: settings.travelAlertPageUid)}"
data-product-details-daytrip-value="{product.daytrip -> f:format.json()}">
<f:render partial="Header/Header" arguments="{layout: 8, subheader: '{product.region.name} / {product.name}', header: product.headline}"/>
<div class="flex flex-col lg:flex-row-reverse lg:items-start"
data-controller="tabs">
@@ -245,92 +246,40 @@
</f:section>
<f:section name="DaytripDateSelect">
<f:comment><!--
<div x-data='daytripDateSelect({
<f:format.raw>
hotelName: "{hotel.name}",
hotelUid: {hotel.uid},
productUid: {product.uid},
contingentEndpointUri: "{ep:uri.ajax(action: 'list', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}",
roomsEndpointUri: "{ep:uri.ajax(action: 'rooms', controller: 'AjaxContingent', format: 'json', pageUid: settings.defaultAjaxUid)}"
</f:format.raw>
})'>
<div class="headerbar" x-text="headerLabel"></div>
<div data-controller="daytrip"
data-daytrip-hotel-uid-value="{hotel.uid}"
data-daytrip-product-uid-value="{product.uid}"
data-daytrip-rooms-uri-value="{ep:uri.ajax(action: 'daytripRooms', controller: 'AjaxTable', format: 'html', pageUid: settings.defaultAjaxUid)}"
data-action="datepicker:range-select->daytrip#load">
<div class="headerbar"
data-daytrip-target="headerLabel"></div>
<div class="grid grid-cols-2 gap-8">
<div class="col-span-2 md:col-span-1">
<div class="date-select">
<div class="date-select__picker">
<div class="daterange__panel">
<input x-ref="field" type="hidden">
<div class="daterange__panel"
data-controller="datepicker"
data-datepicker-min-date-value="{f:format.date(date: 'now', format: 'Y-m-d')}"
data-datepicker-enabled-dates-value='{enabledDates -> f:format.json()}'>
<input type="hidden"
data-datepicker-target="field">
</div>
</div>
</div>
</div>
<div class="col-span-2 md:col-span-1">
<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">
<svg class="w-4 h-4">
<use href="#icon-info-circle"></use>
</svg>
</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">
<div data-daytrip-target="container"></div>
<div class="hidden flex items-center space-x-2"
data-daytrip-target="loadingIndicator">
<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 data-daytrip-target="message">
Bitte einen Zeitraum auswählen.
</div>
</div>
</div>
</div>
--></f:comment>
</f:section>
<f:section name="Calendar">
@@ -6,7 +6,7 @@
<f:section name="Main">
<div data-controller="searchbar"
{f:if(condition: fixed, else: ' data-controller="sticky" data-sticky-offset-value="650" data-sticky-sticky-class="sticky"')}
{f:if(condition: fixed, else: ' data-controller="sticky" data-sticky-offset-value="650"')}
data-action="datepicker:range-select->searchbar#setDateRange"
data-searchbar-uri-value="{ep:uri.ajax(controller: 'Searchbar', action: 'index', pageUid: settings.defaultAjaxUid, format: 'html')}">
<div class="searchbar {f:if(condition: fixed, then: ' sticky')}"