WIP Migrate to stimulus.js and tailwind
This commit is contained in:
@@ -30,18 +30,25 @@ namespace EP\EpProducts\Controller;
|
||||
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\DateService;
|
||||
use EP\EpProducts\Service\FilterService;
|
||||
use EP\EpProducts\Service\ResellerDataService;
|
||||
use EP\EpProducts\Traits\RequestArgumentTypeConversionTrait;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
class AjaxTableController extends ActionController
|
||||
{
|
||||
|
||||
use RequestArgumentTypeConversionTrait;
|
||||
|
||||
/**
|
||||
* @var ProductRepository
|
||||
*/
|
||||
protected $productRepository;
|
||||
|
||||
/**
|
||||
* @var \EP\EpProducts\Service\FilterService
|
||||
*/
|
||||
@@ -64,17 +71,85 @@ class AjaxTableController extends ActionController
|
||||
*/
|
||||
public function __construct
|
||||
(
|
||||
ProductRepository $productRepository,
|
||||
DateService $dateService,
|
||||
FilterService $filterService,
|
||||
ResellerDataService $resellerDataService
|
||||
)
|
||||
{
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->productRepository = $productRepository;
|
||||
$this->dateService = $dateService;
|
||||
$this->filterService = $filterService;
|
||||
$this->resellerDataService = $resellerDataService;
|
||||
}
|
||||
|
||||
public function initializeDatesAction()
|
||||
{
|
||||
if ($this->request->hasArgument('dateFrom')) {
|
||||
$dateFrom = $this->request->getArgument('dateFrom');
|
||||
$this->request->setArgument('dateFrom', $dateFrom === '' ? null : $dateFrom);
|
||||
}
|
||||
if ($this->request->hasArgument('dateTo')) {
|
||||
$dateTo = $this->request->getArgument('dateTo');
|
||||
$this->request->setArgument('dateTo', $dateTo === '' ? null : $dateTo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Product $product
|
||||
* @param Hotel $hotel
|
||||
* @param string $paCode
|
||||
* @param string $dateFrom
|
||||
* @param string $dateTo
|
||||
*
|
||||
* @return string
|
||||
* @throws \Doctrine\DBAL\DBALException
|
||||
*/
|
||||
public function datestableAction(Product $product, Hotel $hotel, $paCode = null, $dateFrom = null, $dateTo = null)
|
||||
{
|
||||
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
|
||||
$filterSettings = $this->filterService->getNormalizedFilterSettings([], $forcedConceptUids);
|
||||
$filterSettings['dateFrom'] = $dateFrom;
|
||||
$filterSettings['dateTo'] = $dateTo;
|
||||
|
||||
$altProductLink = null;
|
||||
$altLabel = null;
|
||||
if ($product->getAltProduct()) {
|
||||
$altProduct = $product->getAltProduct();
|
||||
if ($this->productRepository->hasDates($altProduct)) {
|
||||
/** @var ContentObjectRenderer $contentObject */
|
||||
$contentObject = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
||||
$altProductLink = $contentObject->typoLink_URL(
|
||||
[
|
||||
'parameter' => $altProduct->getDetailPage(),
|
||||
]
|
||||
);
|
||||
$altLabel = $product->getAltLabel();
|
||||
}
|
||||
}
|
||||
if ($reseller = $this->resellerDataService->getResellerForCurrentDomain()) {
|
||||
$paCode = $reseller['paCode'];
|
||||
}
|
||||
$datesTable = $this->dateService->getDatesTable([
|
||||
'product' => $product,
|
||||
'hotel' => $hotel,
|
||||
'filterSettings' => $filterSettings,
|
||||
'template' => $this->settings['bpnBookingUrlTemplateCode'],
|
||||
'paCode' => $paCode,
|
||||
]);
|
||||
$hasSurchargeOrDiscount = $this->dateService->checkSurcharcheOrDiscount($datesTable);
|
||||
|
||||
$this->view->assignMultiple([
|
||||
'dates' => $datesTable,
|
||||
'bookable' => $product->getBookable(),
|
||||
'hotel' => $hotel,
|
||||
'altProductLink' => $altProductLink,
|
||||
'altLabel' => $altLabel,
|
||||
'hasSurcharge' => $hasSurchargeOrDiscount['surcharge'],
|
||||
'hasDiscount' => $hasSurchargeOrDiscount['discount'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Product $product
|
||||
* @param Hotel $hotel
|
||||
@@ -88,7 +163,7 @@ class AjaxTableController extends ActionController
|
||||
(
|
||||
Product $product,
|
||||
Hotel $hotel,
|
||||
Date $date = null,
|
||||
Date $date,
|
||||
$paCode = null
|
||||
) {
|
||||
$forcedConceptUids = GeneralUtility::trimExplode(',', $this->settings['forcedConceptUids'], true);
|
||||
@@ -106,16 +181,20 @@ class AjaxTableController extends ActionController
|
||||
'template' => $this->settings['bpnBookingUrlTemplateCode'],
|
||||
'paCode' => $paCode,
|
||||
]);
|
||||
$hasSurchargeOrDiscount = $this->dateService->checkSurcharcheOrDiscount($priceTable);
|
||||
|
||||
$dateStart = $date ? $date->getDateStart()->format('d.m.Y') : '';
|
||||
$dateEnd = $date ? $date->getDateEnd()->format('d.m.Y') : '';
|
||||
$dateRange = sprintf('%s - %s', $dateStart, $dateEnd);
|
||||
|
||||
return json_encode([
|
||||
$this->view->assignMultiple([
|
||||
'dateRange' => $dateRange,
|
||||
'servicesIncluded' => $date ? $date->getServicesIncluded() : [],
|
||||
'hotel' => $hotel,
|
||||
'servicesIncluded' => $date->getServicesIncluded(),
|
||||
'prices' => $priceTable,
|
||||
], JSON_THROW_ON_ERROR);
|
||||
'hasSurcharge' => $hasSurchargeOrDiscount['surcharge'],
|
||||
'hasDiscount' => $hasSurchargeOrDiscount['discount'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -122,7 +122,7 @@ class DateService implements SingletonInterface
|
||||
'template' => $template,
|
||||
'paCode' => $paCode,
|
||||
'isDayTrip' => $product->isDaytrip(),
|
||||
'isHideBookingButton' => $product->getHideBookingButton(),
|
||||
'showBookingButton' => false === $product->getHideBookingButton(),
|
||||
'nonBookableDateUids' => $nonBookableDateUids,
|
||||
]);
|
||||
}
|
||||
@@ -167,7 +167,7 @@ class DateService implements SingletonInterface
|
||||
'template' => $template,
|
||||
'paCode' => $paCode,
|
||||
'isDayTrip' => $product->isDaytrip(),
|
||||
'isHideBookingButton' => $product->getHideBookingButton(),
|
||||
'showBookingButton' => false === $product->getHideBookingButton(),
|
||||
'nonBookableDateUids' => $nonBookableDateUids,
|
||||
]);
|
||||
}
|
||||
@@ -198,10 +198,10 @@ class DateService implements SingletonInterface
|
||||
public function preprocessPriceTable(array $options): array
|
||||
{
|
||||
$resolver = new OptionsResolver();
|
||||
$resolver->setDefined(['paCode', 'isDayTrip', 'isHideBookingButton', 'nonBookableDateUids']);
|
||||
$resolver->setDefined(['paCode', 'isDayTrip', 'showBookingButton', 'nonBookableDateUids']);
|
||||
$resolver->setRequired(['priceTableData', 'template']);
|
||||
$resolver->setDefault('isDayTrip', false);
|
||||
$resolver->setDefault('isHideBookingButton', false);
|
||||
$resolver->setDefault('showBookingButton', true);
|
||||
$resolvedOptions = $resolver->resolve($options);
|
||||
$priceTable = [];
|
||||
|
||||
@@ -212,7 +212,7 @@ class DateService implements SingletonInterface
|
||||
$nonBookableDateUids = $resolvedOptions['nonBookableDateUids'];
|
||||
|
||||
foreach ($priceTableData as $row) {
|
||||
$isHideBookingButton = $resolvedOptions['isHideBookingButton'] || in_array($row['dateBusProId'], $nonBookableDateUids, false);
|
||||
$showBookingButton = $resolvedOptions['showBookingButton'] || !in_array($row['dateBusProId'], $nonBookableDateUids, false);
|
||||
|
||||
$dateStart = new \DateTime($row['dateStart']);
|
||||
$dateEnd = new \DateTime($row['dateEnd']);
|
||||
@@ -229,23 +229,23 @@ class DateService implements SingletonInterface
|
||||
'dateStart' => $dateStart->format('d.m.Y'),
|
||||
'dateEnd' => $dateEnd->format('d.m.Y'),
|
||||
'season' => $row['season'],
|
||||
'dateBusIncluded' => (bool) $row['dateBusIncluded'],
|
||||
'dateSkipassIncluded' => (bool) $row['dateSkipassIncluded'],
|
||||
'summer' => $row['season'] === 's',
|
||||
'busIncluded' => (bool) $row['dateBusIncluded'],
|
||||
'skipassIncluded' => (bool) $row['dateSkipassIncluded'],
|
||||
'roomUid' => $row['roomUid'],
|
||||
'roomAvailable' => $row['roomAvailable'] && $row['roomPrice'],
|
||||
'roomName' => $row['roomName'],
|
||||
'roomPrice' => $row['roomPrice'],
|
||||
'roomBusPrice' => $row['busPrice'],
|
||||
'roomDiscount' => $row['roomDiscount'],
|
||||
'roomNights' => $row['roomNights'],
|
||||
'roomOptionalServices' => $optionalServices,
|
||||
'roomHasOptionalServices' => count($optionalServices) > 0,
|
||||
'available' => $row['roomAvailable'] && $row['roomPrice'],
|
||||
'minPrice' => $row['roomPrice'],
|
||||
'busPrice' => $row['busPrice'],
|
||||
'discount' => $row['roomDiscount'],
|
||||
'nights' => $row['roomNights'],
|
||||
'optionalServices' => $optionalServices,
|
||||
'hasOptionalServices' => count($optionalServices) > 0,
|
||||
'dateBusProId' => $row['dateBusProId'],
|
||||
'hotelBusProId' => $row['hotelBusProId'],
|
||||
'roomBusProId' => $row['roomBusProId'],
|
||||
'bookingUrl' => $bookingUrl,
|
||||
'isHideBookingButton' => $isHideBookingButton,
|
||||
'showBookingButton' => !$isHideBookingButton,
|
||||
'showBookingButton' => $showBookingButton,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -272,10 +272,10 @@ class DateService implements SingletonInterface
|
||||
public function preprocessDatesTable(array $options): array
|
||||
{
|
||||
$resolver = new OptionsResolver();
|
||||
$resolver->setDefined(['paCode', 'isDayTrip', 'isHideBookingButton', 'nonBookableDateUids']);
|
||||
$resolver->setDefined(['paCode', 'isDayTrip', 'showBookingButton', 'nonBookableDateUids']);
|
||||
$resolver->setRequired(['dateTableData', 'template']);
|
||||
$resolver->setDefault('isDayTrip', false);
|
||||
$resolver->setDefault('isHideBookingButton', false);
|
||||
$resolver->setDefault('showBookingButton', true);
|
||||
$resolver->setDefault('nonBookableDateUids', []);
|
||||
$resolvedOptions = $resolver->resolve($options);
|
||||
|
||||
@@ -288,7 +288,7 @@ class DateService implements SingletonInterface
|
||||
$nonBookableDateUids = $resolvedOptions['nonBookableDateUids'];
|
||||
|
||||
foreach ($dateTableData as $row) {
|
||||
$isHideBookingButton = $resolvedOptions['isHideBookingButton'] || in_array($row['dateBusProId'], $nonBookableDateUids, false);
|
||||
$showBookingButton = $resolvedOptions['showBookingButton'] || !in_array($row['dateBusProId'], $nonBookableDateUids, false);
|
||||
|
||||
$dateStart = new \DateTime($row['dateStart']);
|
||||
$dateEnd = new \DateTime($row['dateEnd']);
|
||||
@@ -304,21 +304,21 @@ class DateService implements SingletonInterface
|
||||
'dateUid' => $row['dateUid'],
|
||||
'dateStart' => $dateStart->format('d.m.y'),
|
||||
'dateEnd' => $dateEnd->format('d.m.y'),
|
||||
'dateMinPrice' => $row['dateMinPrice'],
|
||||
'datePseudoPrice' => $row['datePseudoPrice'],
|
||||
'dateBusPrice' => $row['dateBusPrice'] ?? 0,
|
||||
'dateDiscount' => $row['dateDiscount'] ?? 0,
|
||||
'dateNights' => $row['dateNights'],
|
||||
'dateBusIncluded' => (bool) $row['dateBusIncluded'],
|
||||
'dateSkipassIncluded' => (bool) $row['dateSkipassIncluded'],
|
||||
'dateServicesIncluded' => unserialize($row['dateServicesIncluded'], ['allowed_classes' => false]),
|
||||
'dateServicesGeneral' => unserialize($row['dateServicesGeneral'], ['allowed_classes' => false]),
|
||||
'minPrice' => $row['dateMinPrice'],
|
||||
'pseudoPrice' => $row['datePseudoPrice'],
|
||||
'busPrice' => $row['dateBusPrice'] ?? 0,
|
||||
'discount' => $row['dateDiscount'] ?? 0,
|
||||
'nights' => $row['dateNights'],
|
||||
'busIncluded' => (bool) $row['dateBusIncluded'],
|
||||
'skipassIncluded' => (bool) $row['dateSkipassIncluded'],
|
||||
'servicesIncluded' => unserialize($row['dateServicesIncluded'], ['allowed_classes' => false]),
|
||||
'servicesGeneral' => unserialize($row['dateServicesGeneral'], ['allowed_classes' => false]),
|
||||
'season' => $row['season'],
|
||||
'summer' => $row['season'] === 's',
|
||||
'available' => $row['available'],
|
||||
'unavailable' => $row['available'] === 0,
|
||||
'bookingUrl' => $bookingUrl,
|
||||
'isHideBookingButton' => $isHideBookingButton,
|
||||
'showBookingButton' => !$isHideBookingButton,
|
||||
'showBookingButton' => $showBookingButton,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -420,18 +420,18 @@ class DateService implements SingletonInterface
|
||||
return $priceTable;
|
||||
}
|
||||
|
||||
public function checkSurcharcheOrDiscount(array $datesTableData): array
|
||||
public function checkSurcharcheOrDiscount(array $tableData): array
|
||||
{
|
||||
$data = [
|
||||
'surcharge' => false,
|
||||
'discount' => false,
|
||||
];
|
||||
|
||||
foreach ($datesTableData as $row) {
|
||||
if ($row['dateBusPrice'] && $row['dateBusPrice'] > 0) {
|
||||
foreach ($tableData as $row) {
|
||||
if ($row['busPrice'] && $row['busPrice'] > 0) {
|
||||
$data['surcharge'] = true;
|
||||
}
|
||||
if ($row['dateDiscount'] && $row['dateDiscount'] < 0) {
|
||||
if ($row['discount'] && $row['discount'] < 0) {
|
||||
$data['discount'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,9 +159,10 @@ tx_epproducts_ajax_json {
|
||||
1 = update
|
||||
}
|
||||
AjaxTable {
|
||||
1 = pricetable
|
||||
2 = pricetableHtml
|
||||
3 = eventPricetable
|
||||
1 = datestable
|
||||
2 = pricetable
|
||||
3 = pricetableHtml
|
||||
4 = eventPricetable
|
||||
}
|
||||
AjaxCalendar {
|
||||
1 = range
|
||||
|
||||
@@ -373,7 +373,7 @@ $iconRegistry->registerIcon(
|
||||
'AjaxDate' => 'dates',
|
||||
'AjaxSearchbar' => 'update',
|
||||
'AjaxContingent' => 'list,rooms',
|
||||
'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable',
|
||||
'AjaxTable' => 'datestable,pricetable,pricetableHtml,eventPricetable',
|
||||
'AjaxCalendar' => 'range,contingents,availableRooms',
|
||||
'AjaxWatchlist' => 'list',
|
||||
'AjaxGroupsPrice' => 'processForm',
|
||||
@@ -383,7 +383,7 @@ $iconRegistry->registerIcon(
|
||||
'AjaxDate' => 'dates',
|
||||
'AjaxSearchbar' => 'update',
|
||||
'AjaxContingent' => 'list,rooms',
|
||||
'AjaxTable' => 'pricetable,pricetableHtml,eventPricetable',
|
||||
'AjaxTable' => 'datestable,pricetable,pricetableHtml,eventPricetable',
|
||||
'AjaxCalendar' => 'range,contingents,availableRooms',
|
||||
'AjaxWatchlist' => 'list',
|
||||
'AjaxGroupsPrice' => 'processForm',
|
||||
|
||||
@@ -13,7 +13,6 @@ smoothscroll.polyfill()
|
||||
import Alpine from 'alpinejs'
|
||||
import parseAppConfig from './components/appconfig'
|
||||
import appendPaCode from './components/pacode'
|
||||
import productDetail from './components/product-detail'
|
||||
import daytripDateSelect from './components/daytrip-date-select'
|
||||
import searchBar from './components/search-bar'
|
||||
import calendar from './components/calendar'
|
||||
@@ -22,7 +21,6 @@ import contactForm from './components/contact-form'
|
||||
const appConfig = parseAppConfig()
|
||||
appendPaCode(appConfig)
|
||||
Alpine.store('appconfig', appConfig)
|
||||
Alpine.data('productDetail', productDetail)
|
||||
Alpine.data('daytripDateSelect', daytripDateSelect)
|
||||
Alpine.data('searchBar', searchBar)
|
||||
Alpine.data('calendar', calendar)
|
||||
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
import axios from 'axios'
|
||||
|
||||
export default props => ({
|
||||
uris: props.uris,
|
||||
show: props.hotelOnTop ? 'hotel' : 'dates',
|
||||
tableMode: 'dates',
|
||||
datesRows: [],
|
||||
pricesRows: [],
|
||||
servicesIncluded: [],
|
||||
dateRange: '',
|
||||
available: false,
|
||||
singleDate: false,
|
||||
bookable: true,
|
||||
hasSurcharge: false,
|
||||
hasDiscount: false,
|
||||
altLabel: null,
|
||||
altProductLink: null,
|
||||
loading: false,
|
||||
loaded: false,
|
||||
addBus: false,
|
||||
discountBus: false,
|
||||
showAll: false,
|
||||
init() {
|
||||
// Load dates instantly when initial view is dates
|
||||
if ('dates' === this.show) {
|
||||
this.loadDates()
|
||||
}
|
||||
// Scroll to top when switching table mode
|
||||
this.$watch('tableMode', () => {
|
||||
this.$el.scrollIntoView({ block: 'start', behavior: 'smooth' })
|
||||
})
|
||||
// Load dates (once) when switching to dates view
|
||||
this.$watch('show', () => {
|
||||
if ('dates' === this.show && false === this.loaded) {
|
||||
this.loadDates()
|
||||
}
|
||||
})
|
||||
},
|
||||
loadDates() {
|
||||
this.loading = true
|
||||
axios.get(this.uris.dates)
|
||||
.then(response => {
|
||||
const json = response.data
|
||||
if (json.dates.length === 1) {
|
||||
this.singleDate = true
|
||||
const dateUid = json.dates[0].dateUid
|
||||
this.loadPrices(dateUid)
|
||||
} else {
|
||||
this.singleDate = false
|
||||
this.datesRows = json.dates
|
||||
this.bookable = json.bookable
|
||||
this.altLabel = json.altLabel
|
||||
this.altProductLink = json.altProductLink
|
||||
this.hasSurcharge = json.hasSurcharge
|
||||
this.hasDiscount = json.hasDiscount
|
||||
this.available = json.dates.length > 0
|
||||
this.tableMode = 'dates'
|
||||
}
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
this.loaded = true
|
||||
})
|
||||
},
|
||||
loadPrices(dateUid) {
|
||||
this.loading = true
|
||||
const data = new URLSearchParams({
|
||||
'tx_epproducts_ajax[date]': dateUid,
|
||||
'tx_epproducts_ajax[paCode]': this.$store.appconfig.paCode,
|
||||
})
|
||||
axios.post(this.uris.pricetable, data)
|
||||
.then(response => {
|
||||
const json = response.data
|
||||
this.pricesRows = json.prices
|
||||
this.servicesIncluded = json.servicesIncluded
|
||||
this.dateRange = json.dateRange
|
||||
this.tableMode = 'prices'
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
calculatePrice(minPrice, busPrice, discount) {
|
||||
if (this.addBus) {
|
||||
return minPrice + busPrice
|
||||
}
|
||||
if (this.discountBus) {
|
||||
return minPrice + discount
|
||||
}
|
||||
return minPrice
|
||||
},
|
||||
showBusIcon(busPrice, busIncluded) {
|
||||
return (
|
||||
busPrice && this.addBus ||
|
||||
busIncluded && !this.discountBus
|
||||
)
|
||||
},
|
||||
})
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { Controller } from 'stimulus'
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [ 'content' ]
|
||||
|
||||
open(e) {
|
||||
this.contentTargets.forEach(element => {
|
||||
let uid = element.dataset.uid
|
||||
element.classList.toggle('hidden', e.detail !== uid)
|
||||
})
|
||||
this.element.classList.remove('hidden')
|
||||
}
|
||||
|
||||
close() {
|
||||
this.element.classList.add('hidden', true)
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import { Controller } from 'stimulus'
|
||||
import { useDispatch } from 'stimulus-use'
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [
|
||||
'row',
|
||||
'price',
|
||||
'busIcon',
|
||||
'surchargeToggleLabel',
|
||||
'discountToggleLabel',
|
||||
'rowToggleLabel',
|
||||
'rowToggleIcon',
|
||||
]
|
||||
|
||||
static values = {
|
||||
mode: String,
|
||||
showAllRows: Boolean,
|
||||
}
|
||||
|
||||
initialize() {
|
||||
useDispatch(this)
|
||||
}
|
||||
|
||||
toggleSurcharge() {
|
||||
this.modeValue = this.modeValue === 'surcharge' ? '' : 'surcharge'
|
||||
}
|
||||
|
||||
toggleDiscount() {
|
||||
this.modeValue = this.modeValue === 'discount' ? '' : 'discount'
|
||||
}
|
||||
|
||||
toggleRows() {
|
||||
this.showAllRowsValue = !this.showAllRowsValue
|
||||
}
|
||||
|
||||
openModal(e) {
|
||||
let eventName = `${e.currentTarget.dataset.type}-modal:open`
|
||||
this.dispatch(eventName, e.currentTarget.dataset.uid)
|
||||
}
|
||||
|
||||
modeValueChanged() {
|
||||
this.priceTargets.forEach(price => {
|
||||
let room = parseInt(price.dataset.room)
|
||||
let bus = parseInt(price.dataset.bus)
|
||||
let discount = parseInt(price.dataset.discount)
|
||||
price.innerText = `${this.calculatePrice(room, bus, discount)} €`
|
||||
})
|
||||
this.busIconTargets.forEach(icon => {
|
||||
let busPrice = parseInt(icon.dataset.busPrice)
|
||||
let busIncluded = !!icon.dataset.busIncluded
|
||||
icon.classList.toggle('hidden', this.hideBusIcon(busPrice, busIncluded))
|
||||
})
|
||||
if (this.hasSurchargeToggleLabelTarget) {
|
||||
let labelDefault = this.surchargeToggleLabelTarget.dataset.labelDefault
|
||||
let labelAlt = this.surchargeToggleLabelTarget.dataset.labelAlt
|
||||
this.surchargeToggleLabelTarget.innerText = this.modeValue === 'surcharge' ? labelDefault : labelAlt
|
||||
}
|
||||
if (this.hasDiscountToggleLabelTarget) {
|
||||
let labelDefault = this.discountToggleLabelTarget.dataset.labelDefault
|
||||
let labelAlt = this.discountToggleLabelTarget.dataset.labelAlt
|
||||
this.discountToggleLabelTarget.innerText = this.modeValue === 'discount' ? labelDefault : labelAlt
|
||||
}
|
||||
}
|
||||
|
||||
showAllRowsValueChanged() {
|
||||
let additionalRows = this.rowTargets.slice(5)
|
||||
if (additionalRows.length) {
|
||||
additionalRows.forEach(row => {
|
||||
row.classList.toggle('hidden', this.showAllRowsValue === false)
|
||||
})
|
||||
}
|
||||
if (this.hasRowToggleLabelTarget && this.hasRowToggleIconTarget) {
|
||||
this.rowToggleLabelTarget.innerText = this.showAllRowsValue ? 'weniger Termine anzeigen' : 'alle Termine anzeigen'
|
||||
this.rowToggleIconTarget.setAttribute('href', this.showAllRowsValue ? '#icon-minus-circle' : '#icon-plus-circle')
|
||||
}
|
||||
}
|
||||
|
||||
calculatePrice(minPrice, busPrice, discount) {
|
||||
if ('surcharge' === this.modeValue) {
|
||||
return minPrice + busPrice
|
||||
}
|
||||
if ('discount' === this.modeValue) {
|
||||
return minPrice + discount
|
||||
}
|
||||
return minPrice
|
||||
}
|
||||
|
||||
hideBusIcon(busPrice, busIncluded) {
|
||||
return (
|
||||
busPrice && this.modeValue !== 'surcharge' ||
|
||||
busIncluded && this.modeValue === 'discount'
|
||||
)
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
import { Controller } from 'stimulus'
|
||||
import { useFetch } from '../mixins/use_fetch'
|
||||
import { useConfig } from '../mixins/use_config'
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [
|
||||
'loadingIndicator',
|
||||
'loadingMessage',
|
||||
'datesTableContainer',
|
||||
'priceTableContainer',
|
||||
]
|
||||
|
||||
static values = {
|
||||
datestableUri: String,
|
||||
pricetableUri: String,
|
||||
travelAlertUri: String,
|
||||
loading: Boolean,
|
||||
loaded: Boolean,
|
||||
mode: String,
|
||||
}
|
||||
|
||||
initialize() {
|
||||
useFetch(this)
|
||||
useConfig(this)
|
||||
}
|
||||
|
||||
connect() {
|
||||
if ('dates' === this.modeValue) {
|
||||
this.loadDates()
|
||||
}
|
||||
}
|
||||
|
||||
showDates() {
|
||||
this.modeValue = 'dates'
|
||||
}
|
||||
|
||||
showPrices() {
|
||||
this.modeValue = 'prices'
|
||||
}
|
||||
|
||||
loadDates() {
|
||||
if (true === this.loadedValue) {
|
||||
this.modeValue = 'dates'
|
||||
return
|
||||
}
|
||||
this.loadingValue = true
|
||||
this.datesTableContainerTarget.innerHTML = ''
|
||||
this.modeValue = 'dates'
|
||||
fetch(this.datestableUriValue)
|
||||
.then(this.checkStatus)
|
||||
.then(this.parseHTML)
|
||||
.then(html => {
|
||||
this.datesTableContainerTarget.innerHTML = html
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.loadingValue = false
|
||||
this.loadedValue = true
|
||||
})
|
||||
}
|
||||
|
||||
loadPrices(e) {
|
||||
this.loadingValue = true
|
||||
this.priceTableContainerTarget.innerHTML = ''
|
||||
this.modeValue = 'prices'
|
||||
let paCode = this.getConfig().paCode
|
||||
let dateUid = e.currentTarget.dataset.dateUid
|
||||
const data = new URLSearchParams({
|
||||
'tx_epproducts_ajax[date]': dateUid,
|
||||
'tx_epproducts_ajax[paCode]': paCode,
|
||||
})
|
||||
fetch(this.pricetableUriValue, { method: 'POST', body: data })
|
||||
.then(this.checkStatus)
|
||||
.then(this.parseHTML)
|
||||
.then(html => {
|
||||
this.priceTableContainerTarget.innerHTML = html
|
||||
}).catch(() => {
|
||||
}).finally(() => {
|
||||
this.loadingValue = false
|
||||
})
|
||||
}
|
||||
|
||||
loadingValueChanged() {
|
||||
this.loadingMessageTarget.innerText = this.modeValue === 'prices' ? 'Lade Buchungsptionen...' : 'Lade Termine...'
|
||||
this.loadingIndicatorTarget.classList.toggle('hidden', this.loadingValue === false)
|
||||
}
|
||||
|
||||
modeValueChanged() {
|
||||
this.datesTableContainerTarget.classList.toggle('hidden', this.modeValue === 'prices')
|
||||
this.priceTableContainerTarget.classList.toggle('hidden', this.modeValue === 'dates')
|
||||
this.element.scrollIntoView({ block: 'start', behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { Controller } from 'stimulus'
|
||||
import { useDispatch } from 'stimulus-use'
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [ 'tab', 'button' ]
|
||||
|
||||
initialize() {
|
||||
useDispatch(this)
|
||||
}
|
||||
|
||||
select(e) {
|
||||
e.preventDefault()
|
||||
let selectedTab = e.currentTarget.dataset.tab
|
||||
this.buttonTargets.forEach(element => {
|
||||
let currentTab = element.dataset.tab
|
||||
element.classList.toggle('button--active', selectedTab === currentTab)
|
||||
})
|
||||
this.tabTargets.forEach(element => {
|
||||
let currentTab = element.dataset.tab
|
||||
element.classList.toggle('hidden', selectedTab !== currentTab)
|
||||
})
|
||||
this.dispatch('selected', selectedTab)
|
||||
this.element.scrollIntoView({ block: 'start', behavior: 'smooth' })
|
||||
}
|
||||
|
||||
}
|
||||
+9
-2
@@ -8,7 +8,11 @@ export default class extends Controller {
|
||||
onList: Boolean
|
||||
}
|
||||
|
||||
static targets = [ 'label', 'icon' ]
|
||||
static targets = [
|
||||
'label',
|
||||
'icon',
|
||||
'ariaLabel',
|
||||
]
|
||||
|
||||
initialize() {
|
||||
useDispatch(this)
|
||||
@@ -28,7 +32,10 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
onListValueChanged() {
|
||||
this.labelTarget.setAttribute('aria-label', true === this.onListValue ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen')
|
||||
this.labelTarget.innerText = true === this.onListValue ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen'
|
||||
this.iconTarget.setAttribute('href', true === this.onListValue ? '#icon-minus-circle' : '#icon-plus-circle')
|
||||
if (this.hasAriaLabelTarget) {
|
||||
this.ariaLabelTarget.setAttribute('aria-label', true === this.onListValue ? 'von der Merkliste entfernen' : 'auf die Merkliste setzen')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
.button {
|
||||
@apply inline-flex items-center justify-center px-4 py-2 md:px-8;
|
||||
@apply inline-flex items-center justify-center space-x-2 px-4 py-2 md:px-8;
|
||||
@apply uppercase leading-none;
|
||||
@apply transition-colors outline-none focus:outline-none;
|
||||
@apply bg-ep-secondary border-2 border-ep-secondary text-white;
|
||||
@@ -24,7 +24,7 @@
|
||||
@apply font-bold;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
&.button--active {
|
||||
@apply bg-white border-ep-secondary text-ep-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<span data-microtip-position="top"
|
||||
role="tooltip"
|
||||
aria-label="auf die Merkliste setzen"
|
||||
data-watchlist-toggle-target="label">
|
||||
data-watchlist-toggle-target="ariaLabel">
|
||||
<svg class="w-5 h-5 text-ep-secondary">
|
||||
<use href="#icon-plus-circle" data-watchlist-toggle-target="icon"></use>
|
||||
</svg>
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
<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="headerbar">
|
||||
Buchungsoptionen {hotel.name}
|
||||
</div>
|
||||
|
||||
<div data-controller="pricetable">
|
||||
|
||||
<f:if condition="{hasSurcharge} || {hasDiscount}">
|
||||
<div class="w-full flex justify-end mb-2">
|
||||
<f:if condition="{hasSurcharge}">
|
||||
<button data-action="pricetable#toggleSurcharge"
|
||||
class="flex items-center space-x-1 focus:outline-none text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-arrow-circle-right"></use>
|
||||
</svg>
|
||||
<span data-pricetable-target="surchargeToggleLabel"
|
||||
data-label-default="Preise für Eigenanreise anzeigen"
|
||||
data-label-alt="Preise für Busanreise anzeigen">
|
||||
Preise für Eigenanreise anzeigen
|
||||
</span>
|
||||
</button>
|
||||
</f:if>
|
||||
<f:if condition="{hasDiscount}">
|
||||
<button data-action="pricetable#toggleDiscount"
|
||||
class="flex items-center space-x-1 focus:outline-none text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-arrow-circle-right"></use>
|
||||
</svg>
|
||||
<span data-pricetable-target="discountToggleLabel"
|
||||
data-label-default="Preise für Busanreise anzeigen"
|
||||
data-label-alt="Preise für Eigenanreise anzeigen">
|
||||
Preise für Busanreise anzeigen
|
||||
</span>
|
||||
</button>
|
||||
</f:if>
|
||||
</div>
|
||||
</f:if>
|
||||
|
||||
<div class="w-full max-w-full overflow-x-scroll mb-4">
|
||||
<table class="min-w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="p-1 text-left">
|
||||
Termin
|
||||
</th>
|
||||
<th class="p-1 text-left">
|
||||
<span data-microtip-position="bottom" aria-label="Nächte" role="tooltip">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-bed"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</th>
|
||||
<th class="p-1 text-left whitespace-nowrap">
|
||||
Preis ab
|
||||
</th>
|
||||
<th class="p-1 text-left">
|
||||
inkl.
|
||||
</th>
|
||||
<th class="hidden lg:table-cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<f:for each="{dates}" as="row" iteration="iteration">
|
||||
<tr class="even:bg-gray-200{f:if(condition: '{iteration.cycle} > 5', then: ' hidden')}"
|
||||
data-pricetable-target="row">
|
||||
<td class="px-2 py-1">
|
||||
<f:if condition="{bookable} && {row.available} && {row.showBookingButton}">
|
||||
<f:then>
|
||||
<button class="text-ep-primary-light"
|
||||
data-action="product-details#loadPrices"
|
||||
data-date-uid="{row.dateUid}">
|
||||
{row.dateStart} - {row.dateEnd}
|
||||
</button>
|
||||
<div class="md:hidden">
|
||||
<button class="button--small"
|
||||
data-action="product-details#loadPrices"
|
||||
data-date-uid="{row.dateUid}">
|
||||
Details
|
||||
</button>
|
||||
<a href="{row.bookingUrl}" target="_blank"
|
||||
class="button button--small bg-green-500 border-green-500">
|
||||
Buchen
|
||||
</a>
|
||||
</div>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<span>
|
||||
{row.dateStart} - {row.dateEnd}
|
||||
</span>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</td>
|
||||
<td class="px-2 py-1">
|
||||
{row.nights}
|
||||
</td>
|
||||
<td class="px-2 py-1">
|
||||
<f:if condition="{row.pseudoPrice}">
|
||||
<span class="text-sm line-through">
|
||||
{row.pseudoPrice} €
|
||||
</span>
|
||||
</f:if>
|
||||
<span class="font-bold"
|
||||
data-pricetable-target="price"
|
||||
data-room="{row.minPrice}"
|
||||
data-bus="{row.busPrice}"
|
||||
data-discount="{row.discount}">
|
||||
{row.minPrice} €
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-2 py-1">
|
||||
<div class="hidden md:flex items-center space-x-2">
|
||||
<f:if condition="{row.busIncluded}">
|
||||
<span data-microtip-position="top"
|
||||
aria-label="Busfahrt"
|
||||
role="tooltip"
|
||||
data-pricetable-target="busIcon"
|
||||
data-bus-included="{row.busIncluded}"
|
||||
data-bus-price="{row.busPrice}">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-bus"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</f:if>
|
||||
<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="pricetable#openModal"
|
||||
data-type="services"
|
||||
data-uid="{row.dateUid}"
|
||||
data-microtip-position="top"
|
||||
aria-label="Alle Inklusivleistungen anzeigen"
|
||||
role="tooltip">
|
||||
Leistungen
|
||||
</button>
|
||||
<button class="md:hidden">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-info-circle"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-1 hidden sm:table-cell">
|
||||
<button class="button button--small"
|
||||
data-action="product-details#loadPrices"
|
||||
data-date-uid="{row.dateUid}">
|
||||
Details
|
||||
</button>
|
||||
<f:if condition="{row.available} && {row.showBookingButton}">
|
||||
<a href="{row.bookingUrl}"
|
||||
target="_blank"
|
||||
class="button button--small bg-green-500 border-green-500">
|
||||
Buchen
|
||||
</a>
|
||||
</f:if>
|
||||
<f:if condition="{row.showBookingButton}">
|
||||
<f:else>
|
||||
<a href="#"
|
||||
class="button button--small bg-gray-300">
|
||||
Reisen-Alert
|
||||
</a>
|
||||
</f:else>
|
||||
</f:if>
|
||||
<f:if condition="{row.available} && {row.showBookingButton}">
|
||||
<f:else>
|
||||
<span class="inline-block px-2 mx-1 text-xs text-gray-500">
|
||||
ausgebucht
|
||||
</span>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</td>
|
||||
</tr>
|
||||
</f:for>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<f:if condition="{dates -> f:count()} > 5 || {altProductLink}">
|
||||
<div class="w-full flex justify-between pb-4">
|
||||
<f:if condition="{dates -> f:count()} > 5">
|
||||
<button class="flex items-center space-x-1 focus:outline-none text-ep-primary"
|
||||
data-action="pricetable#toggleRows">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-plus-circle"
|
||||
data-pricetable-target="rowToggleIcon"></use>
|
||||
</svg>
|
||||
<span data-pricetable-target="rowToggleLabel">
|
||||
Alle Termine anzeigen
|
||||
</span>
|
||||
</button>
|
||||
</f:if>
|
||||
<f:if condition="{altProductLink}">
|
||||
<a href="{altProductLink}"
|
||||
title="{altLabel}"
|
||||
class="flex items-center space-x-1 focus:outline-none text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-arrow-circle-right"></use>
|
||||
</svg>
|
||||
<span>
|
||||
{altLabel}
|
||||
</span>
|
||||
</a>
|
||||
</f:if>
|
||||
</div>
|
||||
</f:if>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hidden fixed inset-0 w-full h-full z-50"
|
||||
data-controller="modal"
|
||||
data-action="pricetable: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 {hotel.name}
|
||||
</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="{dates}" as="row">
|
||||
<table class="hidden table table-striped"
|
||||
data-modal-target="content"
|
||||
data-uid="{row.dateUid}">
|
||||
<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>
|
||||
@@ -0,0 +1,254 @@
|
||||
<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="headerbar flex items-center justify-between">
|
||||
<span>Buchungsoptionen</span>
|
||||
<span>{hotel.name} {dateRange}</span>
|
||||
</div>
|
||||
|
||||
<div data-controller="pricetable"
|
||||
data-pricetable-show-all-rows-value="true">
|
||||
|
||||
<f:if condition="{hasSurcharge} || {hasDiscount}">
|
||||
<div class="w-full flex justify-end mb-2">
|
||||
<f:if condition="{hasSurcharge}">
|
||||
<button data-action="pricetable#toggleSurcharge"
|
||||
class="flex items-center space-x-1 focus:outline-none ep:text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-arrow-circle-right"></use>
|
||||
</svg>
|
||||
<span data-pricetable-target="surchargeToggleLabel"
|
||||
data-label-default="Preise für Eigenanreise anzeigen"
|
||||
data-label-alt="Preise für Busanreise anzeigen">
|
||||
Preise für Eigenanreise anzeigen
|
||||
</span>
|
||||
</button>
|
||||
</f:if>
|
||||
<f:if condition="{hasDiscount}">
|
||||
<button data-action="pricetable#toggleDiscount"
|
||||
class="flex items-center space-x-1 focus:outline-none ep:text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-arrow-circle-right"></use>
|
||||
</svg>
|
||||
<span data-pricetable-target="discountToggleLabel"
|
||||
data-label-default="Preise für Busanreise anzeigen"
|
||||
data-label-alt="Preise für Eigenanreise anzeigen">
|
||||
Preise für Busanreise anzeigen
|
||||
</span>
|
||||
</button>
|
||||
</f:if>
|
||||
</div>
|
||||
</f:if>
|
||||
|
||||
<div class="w-full max-w-full overflow-x-scroll mb-4">
|
||||
<table class="min-w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">
|
||||
Zimmertyp
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Preis
|
||||
</th>
|
||||
<th class="text-left">
|
||||
inkl.
|
||||
</th>
|
||||
<th></th>
|
||||
<th class="hidden lg:table-cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<f:for each="{prices}" as="row">
|
||||
<tr class="even:bg-gray-200"
|
||||
data-pricetable-target="row">
|
||||
<td class="px-2 py-1">
|
||||
<f:if condition="{row.showBookingButton} && {row.available}">
|
||||
<f:then>
|
||||
<a href="{row.bookingUrl}"
|
||||
target="_blank">
|
||||
{row.roomName}
|
||||
</a>
|
||||
<a class="button button--small button--action lg:hidden"
|
||||
href="{row.bookingUrl}"
|
||||
target="_blank">
|
||||
Jetzt buchen
|
||||
</a>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<span>
|
||||
{row.roomName}
|
||||
</span>
|
||||
</f:else>
|
||||
</f:if>
|
||||
<f:if condition="{row.available}">
|
||||
<f:else>
|
||||
<div class="lg:hidden">
|
||||
<span class="inline-block px-2 mx-1 text-xs text-gray-500">
|
||||
ausgebucht
|
||||
</span>
|
||||
</div>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</td>
|
||||
<td class="px-2 py-1 whitespace-nowrap"
|
||||
data-pricetable-target="price"
|
||||
data-room="{row.minPrice}"
|
||||
data-bus="{row.busPrice}"
|
||||
data-discount="{row.discount}">
|
||||
{row.minPrice} €
|
||||
</td>
|
||||
<td class="px-2 py-1">
|
||||
<div class="flex items-center space-x-1">
|
||||
<f:if condition="{row.busIncluded}">
|
||||
<span data-microtip-position="top"
|
||||
aria-label="Busfahrt"
|
||||
role="tooltip"
|
||||
data-pricetable-target="busIcon"
|
||||
data-bus-included="{row.busIncluded}"
|
||||
data-bus-price="{row.busPrice}">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-bus"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</f:if>
|
||||
<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>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-1">
|
||||
<f:if condition="{row.hasOptionalServices} && {row.available}">
|
||||
<button class="hidden sm:block button button--light button--small"
|
||||
data-action="pricetable#openModal"
|
||||
data-type="options"
|
||||
data-uid="{row.roomUid}"
|
||||
data-microtip-position="top"
|
||||
aria-label="Alle Zusatzleistungen anzeigen"
|
||||
role="tooltip">
|
||||
Optionen
|
||||
</button>
|
||||
<button class="sm:hidden text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-info-circle"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</f:if>
|
||||
</td>
|
||||
<td class="hidden lg:table-cell px-2 py-1">
|
||||
<f:if condition="{row.isHideBookingButton}">
|
||||
<f:then>
|
||||
<a class="button button--small button--mute"
|
||||
href="#">
|
||||
Reisen-Alert
|
||||
</a>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<f:if condition="{row.available}">
|
||||
<f:then>
|
||||
<a class="button button--small button--action"
|
||||
href="{row.bookingUrl}" target="_blank">
|
||||
Buchen
|
||||
</a>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<span class="inline-block px-2 mx-1 text-xs text-gray-500">
|
||||
ausgebucht
|
||||
</span>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</td>
|
||||
</tr>
|
||||
</f:for>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<f:if condition="{isSingleDate}">
|
||||
<f:else>
|
||||
<button class="button button--small mb-4"
|
||||
data-action="product-details#showDates">
|
||||
<svg class="w-4 h-4 text-white">
|
||||
<use href="#icon-chevron-left"></use>
|
||||
</svg>
|
||||
<span>zurück zur Terminübersicht</span>
|
||||
</button>
|
||||
</f:else>
|
||||
</f:if>
|
||||
<div class="headerbar">
|
||||
Inklusivleistungen
|
||||
</div>
|
||||
<div class="bg-gray-200 p-4 mb-8">
|
||||
<ul class="list-none m-0 p-0">
|
||||
<f:for each="{servicesIncluded}" as="service">
|
||||
<li 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>
|
||||
</li>
|
||||
</f:for>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hidden fixed inset-0 w-full h-full z-50"
|
||||
data-controller="modal"
|
||||
data-action="pricetable:options-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="absolute top-0 left-0 inset-x-0 shadow flex items-center justify-between p-4 bg-ep-primary text-white rounded-t">
|
||||
<span class="text-xl font-bold">
|
||||
Optionale Zusatzleistungen
|
||||
</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="h-128 p-4 mt-12 overflow-y-scroll">
|
||||
<f:for each="{prices}" as="row">
|
||||
<div class="hidden"
|
||||
data-modal-target="content"
|
||||
data-uid="{row.roomUid}">
|
||||
<f:for each="{row.optionalServices}" as="section">
|
||||
<div class="py-2 -mx-1">
|
||||
<div class="font-bold text-xl px-1">
|
||||
{section.label}
|
||||
</div>
|
||||
<f:for each="{section.services}" as="service">
|
||||
<dl class="flex items-start justify-between p-1 hover:bg-gray-200">
|
||||
<dt class="font-normal">
|
||||
{service.label}:
|
||||
</dt>
|
||||
<dd class="whitespace-nowrap">
|
||||
{service.price} €
|
||||
</dd>
|
||||
</dl>
|
||||
</f:for>
|
||||
</div>
|
||||
</f:for>
|
||||
</div>
|
||||
</f:for>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</f:section>
|
||||
|
||||
</html>
|
||||
@@ -9,54 +9,71 @@
|
||||
<article>
|
||||
<f:render partial="Product/HeaderDetail" arguments="{_all}"/>
|
||||
<div class="container"
|
||||
id="detail"
|
||||
x-data='productDetail({
|
||||
uris: {
|
||||
dates: "<ep:uri.ajax action="dates" controller="AjaxDate" arguments="{product: product, hotel: hotel}" format="json" pageUid="{settings.defaultAjaxUid}"/>",
|
||||
pricetable: "<ep:uri.ajax action="pricetable" controller="AjaxTable" arguments="{product: product, hotel: hotel}" format="html" pageUid="{settings.defaultAjaxUid}"/>",
|
||||
travelAlert: "<f:uri.typolink parameter="{settings.travelAlertPageUid}"/>"
|
||||
},
|
||||
hotelOnTop: <f:if condition="{settings.hotelOnTop}"><f:then>true</f:then><f:else>false</f:else></f:if>
|
||||
})'>
|
||||
data-controller="product-details"
|
||||
data-action="tabs:selected->product-details#loadDates"
|
||||
<f:if condition="{settings.hotelOnTop}">
|
||||
<f:else>
|
||||
data-product-details-mode-value="dates"
|
||||
</f:else>
|
||||
</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)}">
|
||||
<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">
|
||||
<div class="flex flex-col lg:flex-row-reverse lg:items-start"
|
||||
data-controller="tabs">
|
||||
<div class="w-full lg:w-1/3">
|
||||
<div class="lg:pl-4">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<f:if condition="{settings.hotelOnTop}">
|
||||
<f:then>
|
||||
<button class="button button--light button--active col-span-1 md:col-span-2"
|
||||
data-action="tabs#select"
|
||||
data-tabs-target="button"
|
||||
data-tab="hotel">
|
||||
Unterkunft
|
||||
</button>
|
||||
<button class="button button--light col-span-1 md:col-span-2"
|
||||
x-bind:class="{ 'button--active': show === 'hotel'}"
|
||||
x-on:click.prevent="show = 'hotel'">Unterkunft</button>
|
||||
data-action="tabs#select"
|
||||
data-tabs-target="button"
|
||||
data-tab="dates">
|
||||
Termine/Preise
|
||||
</button>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<button class="button button--light button--active col-span-1 md:col-span-2"
|
||||
data-action="tabs#select"
|
||||
data-tabs-target="button"
|
||||
data-tab="dates">
|
||||
Termine/Preise
|
||||
</button>
|
||||
<button class="button button--light col-span-1 md:col-span-2"
|
||||
x-bind:class="{ 'button--active': show === 'dates'}"
|
||||
x-on:click.prevent="show = 'dates'">Termine/Preise</button>
|
||||
</f:else>
|
||||
</f:if>
|
||||
<f:if condition="{settings.hotelOnTop}">
|
||||
<f:then>
|
||||
<button class="button button--light col-span-1 md:col-span-2"
|
||||
x-bind:class="{ 'button--active': show === 'dates'}"
|
||||
x-on:click.prevent="show = 'dates'">Termine/Preise</button>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<button class="button button--light col-span-1 md:col-span-2"
|
||||
x-bind:class="{ 'button--active': show === 'hotel'}"
|
||||
x-on:click.prevent="show = 'hotel'">Unterkunft</button>
|
||||
data-action="tabs#select"
|
||||
data-tabs-target="button"
|
||||
data-tab="hotel">
|
||||
Unterkunft
|
||||
</button>
|
||||
</f:else>
|
||||
</f:if>
|
||||
<button class="button button--light col-span-1 md:col-span-2"
|
||||
x-bind:class="{ 'button--active': show === 'region'}"
|
||||
x-on:click.prevent="show = 'region'">Gebiet</button>
|
||||
data-action="tabs#select"
|
||||
data-tabs-target="button"
|
||||
data-tab="region">
|
||||
Gebiet
|
||||
</button>
|
||||
<button class="button button--light col-span-1 md:col-span-2"
|
||||
x-bind:class="{ 'button--active': show === 'city'}"
|
||||
x-on:click.prevent="show = 'city'">Ort</button>
|
||||
data-action="tabs#select"
|
||||
data-tabs-target="button"
|
||||
data-tab="city">
|
||||
Ort
|
||||
</button>
|
||||
<f:if condition="{product.journey}">
|
||||
<button class="button button--light col-span-1 md:col-span-2"
|
||||
x-bind:class="{ 'button--active': show === 'journey'}"
|
||||
x-on:click.prevent="show = 'journey'">Anreise</button>
|
||||
data-action="tabs#select"
|
||||
data-tabs-target="button"
|
||||
data-tab="journey">
|
||||
Anreise
|
||||
</button>
|
||||
</f:if>
|
||||
<f:if condition="{product.faqs}">
|
||||
<button class="button button--light col-span-1 md:col-span-2"
|
||||
@@ -68,17 +85,17 @@
|
||||
</f:if>
|
||||
<div class="col-span-2">
|
||||
<p class="mb-4">
|
||||
<button class="flex items-center space-x-2"
|
||||
<button class="button button--light w-full"
|
||||
data-controller="watchlist-toggle"
|
||||
data-watchlist-toggle-key-value="{hotel.uid}:{product.uid}"
|
||||
data-action="watchlist-toggle#toggle">
|
||||
<span data-microtip-position="top" aria-label="" role="tooltip" data-watchlist-toggle-target="label">
|
||||
<svg class="w-5 h-5">
|
||||
<use href="#icon-plus-circle"
|
||||
data-watchlist-toggle-target="icon"></use>
|
||||
</svg>
|
||||
<span data-watchlist-toggle-target="label">
|
||||
Auf die Merkliste setzen
|
||||
</span>
|
||||
<span></span>
|
||||
</button>
|
||||
</p>
|
||||
<p class="mb-4">
|
||||
@@ -98,17 +115,18 @@
|
||||
<div id="main" class="w-full lg:w-2/3">
|
||||
<div class="lg:pr-4">
|
||||
<!-- Loader -->
|
||||
<div x-show="loading"
|
||||
x-cloak
|
||||
class="flex items-center space-x-2 mb-4">
|
||||
<div class="flex items-center space-x-2 mb-4 hidden border border-ep-primary p-2"
|
||||
data-product-details-target="loadingIndicator">
|
||||
<f:image class="block w-4 h-4" src="EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif" alt="Loading"/>
|
||||
<span>Loading...</span>
|
||||
<span data-product-details-target="loadingMessage">
|
||||
Loading...
|
||||
</span>
|
||||
</div>
|
||||
<!-- /Loader -->
|
||||
<!-- Dates -->
|
||||
<div x-show="show === 'dates' && !loading"
|
||||
x-transition.opacity
|
||||
x-cloak>
|
||||
<div class="{f:if(condition: settings.hotelOnTop, then: 'hidden')}"
|
||||
data-tabs-target="tab"
|
||||
data-tab="dates">
|
||||
<f:if condition="{isProductWithExcludedConcept}">
|
||||
<f:if condition="{product.region.season} == 'w'">
|
||||
<div class="alert alert-info">
|
||||
@@ -125,12 +143,8 @@
|
||||
<f:render section="DaytripDateSelect" arguments="{_all}"/>
|
||||
</f:then>
|
||||
<f:else>
|
||||
<div x-show="tableMode === 'dates'">
|
||||
<f:render section="DatesTable" arguments="{_all}"/>
|
||||
</div>
|
||||
<div x-show="tableMode === 'prices'">
|
||||
<f:render section="PriceTable" arguments="{_all}"/>
|
||||
</div>
|
||||
<div class="hidden" data-product-details-target="datesTableContainer"></div>
|
||||
<div class="hidden" data-product-details-target="priceTableContainer"></div>
|
||||
<f:if condition="{isProductWithNoInfoConcept}">
|
||||
<f:else>
|
||||
<template x-if="!available">
|
||||
@@ -140,28 +154,34 @@
|
||||
</f:if>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</div>
|
||||
<!-- /Dates -->
|
||||
<div x-show="show === 'dates' || show === 'prices'" x-cloak>
|
||||
<f:render partial="Product/Details" arguments="{_all}"/>
|
||||
</div>
|
||||
<!-- /Dates -->
|
||||
<!-- Hotel -->
|
||||
<div x-show="show === 'hotel'" x-transition.opacity x-cloak>
|
||||
<div class="{f:if(condition: settings.hotelOnTop, else: 'hidden')}"
|
||||
data-tabs-target="tab"
|
||||
data-tab="hotel">
|
||||
<f:render partial="Hotel/Details" arguments="{hotel: hotel, bars: 1, showNonBookableHint: 0}"/>
|
||||
</div>
|
||||
<!-- /Hotel -->
|
||||
<!-- City -->
|
||||
<div x-show="show === 'city'" x-transition.opacity x-cloak>
|
||||
<div class="hidden"
|
||||
data-tabs-target="tab"
|
||||
data-tab="city">
|
||||
<f:render partial="City/Details" arguments="{city: product.city, bars: 1}"/>
|
||||
</div>
|
||||
<!-- /City -->
|
||||
<!-- Region -->
|
||||
<div x-show="show === 'region'" x-transition.opacity x-cloak>
|
||||
<div class="hidden"
|
||||
data-tabs-target="tab"
|
||||
data-tab="region">
|
||||
<f:render partial="Region/Details" arguments="{region: product.region, bars: 1}"/>
|
||||
</div>
|
||||
<!-- /Region -->
|
||||
<!-- Journey -->
|
||||
<div x-show="show === 'journey'" x-transition.opacity x-cloak>
|
||||
<div class="hidden"
|
||||
data-tabs-target="tab"
|
||||
data-tab="journey">
|
||||
<f:render partial="Journey/Details" arguments="{journey: product.journey, bars: 1}"/>
|
||||
</div>
|
||||
<!-- /Journey -->
|
||||
@@ -173,8 +193,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<div x-data x-init='fbq("track", "ViewContent", { "content_ids": [ {product.busProId -> f:format.raw()} ], "content_name": "{product.nameInternal -> f:format.raw()}", "content_type": "product" })'></div>
|
||||
<div data-controller="fbpixel"
|
||||
data-fbpixel-props-value='{ "content_ids": [ {product.busProId -> f:format.raw()} ], "content_name": "{product.nameInternal -> f:format.raw()}", "content_type": "product" }'></div>
|
||||
<div id="faq">
|
||||
<f:render partial="Faq" arguments="{faqs: product.faqs, settings: settings}"/>
|
||||
</div>
|
||||
</f:section>
|
||||
|
||||
<f:section name="Sidebar">
|
||||
@@ -219,360 +242,8 @@
|
||||
</f:if>
|
||||
</f:section>
|
||||
|
||||
<f:section name="DatesTable">
|
||||
<div x-show="bookable"
|
||||
x-cloak>
|
||||
<div class="headerbar">
|
||||
Buchungsoptionen {hotel.name}
|
||||
</div>
|
||||
<f:render section="PriceToggle"/>
|
||||
<div class="w-full max-w-full overflow-x-scroll mb-4">
|
||||
<table class="min-w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="p-1 text-left">
|
||||
Termin
|
||||
</th>
|
||||
<th class="p-1 text-left">
|
||||
<span data-microtip-position="bottom" aria-label="Nächte" role="tooltip">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-bed"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</th>
|
||||
<th class="p-1 text-left whitespace-nowrap">
|
||||
Preis ab
|
||||
</th>
|
||||
<th class="p-1 text-left">
|
||||
inkl.
|
||||
</th>
|
||||
<th class="hidden lg:table-cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="(row, index) in datesRows" x-bind:key="row.dateUid">
|
||||
<tr class="even:bg-gray-200" x-bind:class="{ 'hidden': index > 4 && !showAll }">
|
||||
<td class="px-2 py-1">
|
||||
<template x-if="bookable && row.available && !row.isHideBookingButton">
|
||||
<a class="text-ep-primary-light" href="#"
|
||||
x-on:click.prevent="loadPrices(row.dateUid)"
|
||||
x-text="row.dateStart + ' - ' + row.dateEnd"></a>
|
||||
<div class="md:hidden">
|
||||
<button x-on:click.prevent="loadPrices(row.dateUid)" class="button button--small">
|
||||
Details
|
||||
</button>
|
||||
<a x-bind:href="row.bookingUrl" target="_blank" class="button button--small bg-green-500 border-green-500">
|
||||
Buchen
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="!bookable || !row.available || row.isHideBookingButton">
|
||||
<span x-text="row.dateStart + ' - ' + row.dateEnd"></span>
|
||||
</template>
|
||||
</td>
|
||||
<td class="px-2 py-1"
|
||||
x-text="row.dateNights"></td>
|
||||
<td class="px-2 py-1">
|
||||
<span class="text-sm line-through" x-show="row.datePseudoPrice" x-text="row.datePseudoPrice + ' €'"></span>
|
||||
<strong x-text="calculatePrice(row.dateMinPrice, row.dateBusPrice, row.dateDiscount) + ' €'"></strong>
|
||||
</td>
|
||||
<td class="px-2 py-1">
|
||||
<div class="hidden md:flex items-center space-x-2">
|
||||
<div x-show="showBusIcon(row.dateBusPrice, row.dateBusIncluded)" data-microtip-position="top" aria-label="Busfahrt inklusive" role="tooltip">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-bus"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div x-show="row.dateSkipassIncluded" data-microtip-position="top" x-bind:aria-label="row.season === 's' ? 'Bergbahnticket inklusive' : 'Skipass inklusive'" role="tooltip">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-tag"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<button class="button button--light button--small hidden md:block"
|
||||
data-microtip-position="top" aria-label="Alle Inklusivleistungen anzeigen" role="tooltip">
|
||||
Leistungen
|
||||
</button>
|
||||
<button class="md:hidden">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-info-circle"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-1 hidden sm:table-cell">
|
||||
<button x-show="row.available || row.isHideBookingButton" class="button button--small"
|
||||
x-on:click.prevent="loadPrices(row.dateUid)">
|
||||
Details
|
||||
</button>
|
||||
<a x-show="row.available && !row.isHideBookingButton" x-bind:href="row.bookingUrl"
|
||||
target="_blank"
|
||||
class="button button--small bg-green-500 border-green-500">
|
||||
Buchen
|
||||
</a>
|
||||
<a x-show="row.isHideBookingButton" x-bind:href="uris.travelAlertUrl"
|
||||
class="button button--small bg-gray-300">
|
||||
Reisen-Alert
|
||||
</a>
|
||||
<span x-show="!row.available && !row.isHideBookingButton"
|
||||
class="inline-block px-2 mx-1 text-xs text-gray-500">
|
||||
ausgebucht
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<template x-if="datesRows.length > 5 || altProductLink">
|
||||
<div class="grid grid-cols-2 gap-8 pb-4">
|
||||
<button x-show="datesRows.length > 5"
|
||||
x-on:click.prevent="showAll = !showAll"
|
||||
class="flex items-center space-x-1 focus:outline-none ep:text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use x-bind:href="showAll ? '#icon-minus-circle' : '#icon-plus-circle'"></use>
|
||||
</svg>
|
||||
<span x-text="showAll ? 'Weniger Termine anzeigen' : 'Alle Termine anzeigen'"></span>
|
||||
</button>
|
||||
<template x-if="altProductLink">
|
||||
<a x-bind:href="altProductLink"
|
||||
x-bind:title="altLabel"
|
||||
class="flex items-center space-x-1 focus:outline-none ep:text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-arrow-circle-right"></use>
|
||||
</svg>
|
||||
<span x-text="altLabel"></span>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template x-for="row in datesRows">
|
||||
<div x-bind:id="'services-' + row.dateUid"
|
||||
x-transition:enter="transition duration-200 ease-in-out"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition duration-200 ease-in-out"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
x-cloak
|
||||
class="hidden fixed inset-0 w-full h-full z-50">
|
||||
<div class="absolute inset-0 w-full h-full bg-overlay-black"></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 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>
|
||||
|
||||
<f:section name="PriceTable">
|
||||
<div class="headerbar flex items-center justify-between">
|
||||
<span>Buchungsoptionen</span>
|
||||
<span x-text="dateRange"></span>
|
||||
</div>
|
||||
<f:render section="PriceToggle"/>
|
||||
<div class="w-full max-w-full overflow-x-scroll mb-4">
|
||||
<table class="min-w-full border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">
|
||||
Zimmertyp
|
||||
</th>
|
||||
<th class="text-left">
|
||||
Preis
|
||||
</th>
|
||||
<th class="text-left">
|
||||
inkl.
|
||||
</th>
|
||||
<th></th>
|
||||
<th class="hidden lg:table-cell"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="(row, index) in pricesRows" x-bind:key="row.roomBusProId">
|
||||
<tr class="even:bg-gray-200">
|
||||
<td class="px-2 py-1">
|
||||
<span x-show="row.isHideBookingButton || !row.roomAvailable" x-text="row.roomName"></span>
|
||||
<a x-show="!row.isHideBookingButton && row.roomAvailable"
|
||||
x-bind:href="row.bookingUrl" target="_blank"
|
||||
x-text="row.roomName"></a>
|
||||
<a class="button button--small button--action lg:hidden"
|
||||
x-show="!row.isHideBookingButton && row.roomAvailable"
|
||||
x-bind:href="row.bookingUrl" target="_blank">
|
||||
Jetzt buchen
|
||||
</a>
|
||||
<div class="lg:hidden"
|
||||
x-show="!row.roomAvailable">
|
||||
<span class="inline-block px-2 mx-1 text-xs text-gray-500">
|
||||
ausgebucht
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-1 whitespace-nowrap" x-bind:class="!row.roomAvailable && !row.isHideBookingButton && 'line-through'"
|
||||
x-text="calculatePrice(row.roomPrice, row.roomBusPrice, row.roomDiscount) + ' €'"></td>
|
||||
<td class="px-2 py-1">
|
||||
<div class="flex items-center space-x-1">
|
||||
<span x-show="showBusIcon(row.roomBusPrice, row.dateBusIncluded)" data-microtip-position="top" aria-label="Busfahrt inklusive" role="tooltip">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-bus"></use>
|
||||
</svg>
|
||||
</span>
|
||||
<span x-show="row.dateSkipassIncluded" data-microtip-position="top" x-bind:aria-label="row.season === 's' ? 'Bergbahnticket inklusive' : 'Skipass inklusive'" role="tooltip">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-tag"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-2 py-1">
|
||||
<button x-show="row.roomHasOptionalServices && row.roomAvailable"
|
||||
class="hidden sm:block button button--light button--small"
|
||||
data-microtip-position="top" aria-label="Alle Zusatzleistungen anzeigen" role="tooltip">
|
||||
Optionen
|
||||
</button>
|
||||
<button x-show="row.roomHasOptionalServices && row.roomAvailable"
|
||||
class="sm:hidden ep:text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-info-circle"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
<td class="hidden lg:table-cell px-2 py-1">
|
||||
<a class="button button--small button--action"
|
||||
x-show="row.roomAvailable && !row.isHideBookingButton"
|
||||
x-bind:href="row.bookingUrl" target="_blank">
|
||||
Buchen
|
||||
</a>
|
||||
<a class="button button--small button--mute"
|
||||
x-show="row.isHideBookingButton"
|
||||
x-bind:href="uris.travelAlertUrl">
|
||||
Reisen-Alert
|
||||
</a>
|
||||
<span x-show="!row.roomAvailable && !row.isHideBookingButton"
|
||||
class="inline-block px-2 mx-1 text-xs text-gray-500">
|
||||
ausgebucht
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<template x-for="row in pricesRows">
|
||||
<div x-bind:id="'optional-services-' + row.roomUid"
|
||||
x-transition:enter="transition duration-200 ease-in-out"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition duration-200 ease-in-out"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="hidden 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"></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 pb-2 mb-4">
|
||||
<span class="text-2xl font-bold">
|
||||
Optionale Zusatzleistungen
|
||||
</span>
|
||||
<button 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="py-2 -mx-1">
|
||||
<div class="font-bold text-xl px-1" x-text="section.label"></div>
|
||||
<template x-for="optionalService in section.services">
|
||||
<dl class="flex items-start justify-between p-1 hover:bg-gray-200">
|
||||
<dt class="font-normal" x-text="optionalService.label + ':'"></dt>
|
||||
<dd class="whitespace-nowrap" x-text="optionalService.price + ' €'"></dd>
|
||||
</dl>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="!singleDate">
|
||||
<button class="button button--small mb-4"
|
||||
x-on:click.prevent="tableMode = 'dates'">
|
||||
<svg class="w-4 h-4 text-white">
|
||||
<use href="#icon-chevron-left"></use>
|
||||
</svg>
|
||||
<span>zurück zur Terminübersicht</span>
|
||||
</button>
|
||||
</template>
|
||||
<div class="headerbar">
|
||||
Inklusivleistungen
|
||||
</div>
|
||||
<div class="bg-gray-200 p-4 mb-8">
|
||||
<ul class="list-none m-0 p-0">
|
||||
<template x-for="service in servicesIncluded">
|
||||
<li class="flex items-center space-x-2">
|
||||
<svg class="w-6 h-6 text-ep-primary">
|
||||
<use href="#icon-check"></use>
|
||||
</svg>
|
||||
<span x-text="service"></span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</f:section>
|
||||
|
||||
<f:section name="PriceToggle">
|
||||
<div class="w-full flex justify-end mb-2" x-show="hasSurcharge || hasDiscount">
|
||||
<template x-if="hasSurcharge">
|
||||
<button x-on:click.prevent="addBus = !addBus"
|
||||
class="flex items-center space-x-1 focus:outline-none ep:text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-arrow-circle-right"></use>
|
||||
</svg>
|
||||
<span x-show="addBus">
|
||||
Preise für Eigenanreise anzeigen
|
||||
</span>
|
||||
<span x-show="! addBus">
|
||||
Preise für Busanreise anzeigen
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
<template x-if="hasDiscount">
|
||||
<button x-on:click.prevent="discountBus = !discountBus"
|
||||
class="flex items-center space-x-1 focus:outline-none ep:text-ep-primary">
|
||||
<svg class="w-4 h-4">
|
||||
<use href="#icon-arrow-circle-right"></use>
|
||||
</svg>
|
||||
<span x-show="discountBus">
|
||||
Preise für Busanreise anzeigen
|
||||
</span>
|
||||
<span x-show="!discountBus">
|
||||
Preise für Eigenanreise anzeigen
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</f:section>
|
||||
|
||||
<f:section name="DaytripDateSelect">
|
||||
<f:comment><!--
|
||||
<div x-data='daytripDateSelect({
|
||||
<f:format.raw>
|
||||
hotelName: "{hotel.name}",
|
||||
@@ -657,6 +328,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
--></f:comment>
|
||||
</f:section>
|
||||
|
||||
<f:section name="Calendar">
|
||||
@@ -673,23 +345,19 @@
|
||||
<span class="block font-bold mb-2">
|
||||
Belegungskalender
|
||||
</span>
|
||||
<div x-data='calendar({
|
||||
<f:format.raw>
|
||||
url: "{ep:uri.ajax(action: 'range', controller: 'AjaxCalendar', extensionName: 'epproducts', pageUid: settings.defaultAjaxUid, format: 'html')}",
|
||||
hotel: {product.calendarHotel.uid}
|
||||
</f:format.raw>
|
||||
})'>
|
||||
<div data-controller="calendar"
|
||||
data-calendar-uri-value="{ep:uri.ajax(action: 'range', controller: 'AjaxCalendar', extensionName: 'epproducts', pageUid: settings.defaultAjaxUid, format: 'html')}"
|
||||
data-calendar-hotel-uid-value="{product.calendarHotel.uid}">
|
||||
<select class="w-full border-gray-400 focus:border-gray-400"
|
||||
x-model="selectedIndex"
|
||||
x-on:change="load">
|
||||
data-action="calendar#selectMonth">
|
||||
<template x-for="(month, index) in selectableMonths">
|
||||
<option x-bind:value="index" x-text="month.toLocaleDateString('de-DE', {year: 'numeric', month: 'long'})"></option>
|
||||
<option value="index" data-text="month.toLocaleDateString('de-DE', {year: 'numeric', month: 'long'})"></option>
|
||||
</template>
|
||||
</select>
|
||||
<div class="mt-4"
|
||||
x-ref="calendarWrapper"
|
||||
x-cloak></div>
|
||||
<div class="absolute top-0 left-0 inset-0 w-full h-full bg-overlay-white" x-show="loading">
|
||||
data-calendar-target="container"></div>
|
||||
<div class="absolute top-0 left-0 inset-0 w-full h-full bg-overlay-white"
|
||||
data-calendar-target="loadingIndicator">
|
||||
<f:image class="absolute top-1/2 left-1/2 -translate-xy-1/2" src="EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif" alt="Loading"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+30
-32
@@ -3,8 +3,6 @@ const tinycolor = require('tinycolor2')
|
||||
|
||||
module.exports = {
|
||||
purge: {
|
||||
mode: 'layers',
|
||||
layers: ['components', 'utilities'],
|
||||
content: [
|
||||
'./public/typo3conf/ext/ep_theme/Configuration/TypoScript/**/*.typoscript',
|
||||
'./public/typo3conf/ext/ep_theme/Resources/Private/Layouts/**/*.html',
|
||||
@@ -34,27 +32,27 @@ module.exports = {
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
'overlay': {
|
||||
'black': tinycolor('#000000').setAlpha(0.85).toRgbString(),
|
||||
'white': tinycolor('#ffffff').setAlpha(0.85).toRgbString(),
|
||||
overlay: {
|
||||
black: tinycolor('#000000').setAlpha(0.85).toRgbString(),
|
||||
white: tinycolor('#ffffff').setAlpha(0.85).toRgbString(),
|
||||
},
|
||||
'concept': {
|
||||
'default': '#005ca9',
|
||||
'bdg': '#005ca9',
|
||||
'skr': '#005ca9',
|
||||
'evt': '#ed5454',
|
||||
'fam': '#7ca7d5',
|
||||
'grp': '#ffa300',
|
||||
'prg': '#005ca9',
|
||||
'ind': '#009fe3',
|
||||
'wkd': '#44d04e',
|
||||
'spz': '#436a8a',
|
||||
'epe': '#436a8a',
|
||||
'sfl': '#999',
|
||||
concept: {
|
||||
default: '#005ca9',
|
||||
bdg: '#005ca9',
|
||||
skr: '#005ca9',
|
||||
evt: '#ed5454',
|
||||
fam: '#7ca7d5',
|
||||
grp: '#ffa300',
|
||||
prg: '#005ca9',
|
||||
ind: '#009fe3',
|
||||
wkd: '#44d04e',
|
||||
spz: '#436a8a',
|
||||
epe: '#436a8a',
|
||||
sfl: '#999',
|
||||
},
|
||||
'ep': {
|
||||
'primary': '#3d8ccb',
|
||||
'secondary': '#f9c700',
|
||||
ep: {
|
||||
primary: '#3d8ccb',
|
||||
secondary: '#f9c700',
|
||||
'primary-light': '#009fff',
|
||||
'primary-medium': '#165883',
|
||||
'primary-dark': '#18527B',
|
||||
@@ -64,25 +62,25 @@ module.exports = {
|
||||
'secondary-10': tinycolor('#f0bc44').setAlpha(0.1).toRgbString(),
|
||||
'secondary-80': tinycolor('#f0bc44').setAlpha(0.8).toRgbString(),
|
||||
},
|
||||
'sbw': {
|
||||
'primary': '#f0bc44',
|
||||
'secondary': '#ee525a',
|
||||
sbw: {
|
||||
primary: '#f0bc44',
|
||||
secondary: '#ee525a',
|
||||
'primary-dark': '#c7a20c',
|
||||
'primary-10': tinycolor('#f0bc44').setAlpha(0.1).toRgbString(),
|
||||
'primary-50': tinycolor('#f0bc44').setAlpha(0.5).toRgbString(),
|
||||
'primary-80': tinycolor('#f0bc44').setAlpha(0.8).toRgbString(),
|
||||
'overlay-secondary': tinycolor('#c20000').setAlpha(0.55).toRgbString(),
|
||||
},
|
||||
'snz': {
|
||||
'primary': '#82C3C6',
|
||||
'secondary': '#f9c33d',
|
||||
snz: {
|
||||
primary: '#82C3C6',
|
||||
secondary: '#f9c33d',
|
||||
'primary-10': tinycolor('#82C3C6').setAlpha(0.1).toRgbString(),
|
||||
'primary-50': tinycolor('#82C3C6').setAlpha(0.5).toRgbString(),
|
||||
'primary-80': tinycolor('#82C3C6').setAlpha(0.8).toRgbString(),
|
||||
},
|
||||
'uch': {
|
||||
'primary': '#b5e400',
|
||||
'secondary': '#19a3dc',
|
||||
uch: {
|
||||
primary: '#b5e400',
|
||||
secondary: '#19a3dc',
|
||||
'primary-dark': '#b5e400',
|
||||
'primary-10': tinycolor('#b5e400').setAlpha(0.1).toRgbString(),
|
||||
'primary-50': tinycolor('#b5e400').setAlpha(0.5).toRgbString(),
|
||||
@@ -122,11 +120,11 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
require('@tailwindcss/forms'),
|
||||
require('tailwindcss-alt')({
|
||||
variants: ['sbw', 'snz', 'uch'],
|
||||
}),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
require('@tailwindcss/forms'),
|
||||
plugin(function ({addVariant, e}) {
|
||||
addVariant('first-type', ({modifySelectors, separator}) => {
|
||||
modifySelectors(({className}) => {
|
||||
|
||||
Reference in New Issue
Block a user