Merge branch 'master' into feature/a11y-improvements

This commit is contained in:
Björn Fromme
2025-06-22 11:03:45 +02:00
64 changed files with 1463 additions and 708 deletions
@@ -28,6 +28,7 @@ namespace EP\EpProducts\Controller;
use EP\EpProducts\Domain\Model\Dto\GroupsPriceInquiry;
use EP\EpProducts\Domain\Model\GroupsPriceBoard;
use EP\EpProducts\Domain\Model\GroupsPriceOption;
use EP\EpProducts\Domain\Model\Hotel;
use EP\EpProducts\Domain\Repository\GroupsPriceOptionRepository;
use EP\EpProducts\Service\EmailService;
@@ -69,6 +70,9 @@ class AjaxGroupsPriceController extends ActionController
public function processFormAction(Hotel $hotel, GroupsPriceInquiry $groupsPriceInquiry)
{
$mode = $groupsPriceInquiry->getMode();
$country = $hotel->getCountry()->getCode();
$effectivePax = $groupsPriceInquiry->getEffectivePax();
$nights = $groupsPriceInquiry->getNights();
if (false === in_array($mode, [GroupsPriceInquiry::MODE_BOOKING, GroupsPriceInquiry::MODE_INQUIRY])) {
$mode = GroupsPriceInquiry::MODE_BOOKING;
@@ -86,10 +90,25 @@ class AjaxGroupsPriceController extends ActionController
'templateName' => 'GroupsPrice',
];
$optionPrice = function (GroupsPriceOption $option) use ($country, $effectivePax, $nights) {
$price = 'CH' === $country ? $option->getPriceChf() : $option->getPrice();
$currency = 'CH' === $country ? 'CHF' : '€';
if (GroupsPriceOption::TYPE_PER_PAX === $option->getType()) {
$price = $price * $effectivePax;
} elseif (GroupsPriceOption::TYPE_PER_NIGHT === $option->getType()) {
$price = $price * $nights;
} elseif (GroupsPriceOption::TYPE_PER_PAX_AND_NIGHT === $option->getType()) {
$price = $price * $nights * $effectivePax;
}
return $price.' '.$currency;
};
try {
$options = $this->groupsPriceOptionRepository->findByUids($groupsPriceInquiry->getOptions());
$selectedOptions = array_map(function ($option) {
return $option->getTitle();
$selectedOptions = array_map(function ($option) use ($optionPrice) {
return $option->getTitle().': '.$optionPrice($option);
}, $options);
}
catch (InvalidQueryException $e) {
@@ -101,13 +120,20 @@ class AjaxGroupsPriceController extends ActionController
'hotel' => $hotel,
'name' => $groupsPriceInquiry->getName(),
'group' => $groupsPriceInquiry->getGroup(),
'address' => $groupsPriceInquiry->getAddress(),
'street' => $groupsPriceInquiry->getStreet(),
'postcode' => $groupsPriceInquiry->getPostcode(),
'city' => $groupsPriceInquiry->getCity(),
'email' => $groupsPriceInquiry->getEmail(),
'phone' => $groupsPriceInquiry->getPhone(),
'remarks' => $groupsPriceInquiry->getRemarks(),
'dateFrom' => $groupsPriceInquiry->getDateFrom(),
'dateTo' => $groupsPriceInquiry->getDateTo(),
'nights' => $groupsPriceInquiry->getNights(),
'pax' => $groupsPriceInquiry->getPax(),
'children' => $groupsPriceInquiry->getChildren(),
'minors' => $groupsPriceInquiry->getMinors(),
'adolescents' => $groupsPriceInquiry->getAdolescents(),
'adolescentsAge' => $hotel->getGroupsPriceAdolescentsAge(),
'options' => $selectedOptions,
'board' => $groupsPriceInquiry->getBoard(),
'summary' => $groupsPriceInquiry->getSummary(),
@@ -124,8 +150,7 @@ class AjaxGroupsPriceController extends ActionController
protected function errorAction() {
$formErrors = [];
if ($this->arguments->validate()->hasErrors()) {
foreach ($this->arguments->validate()->getFlattenedErrors() as $key => $errors)
{
foreach ($this->arguments->validate()->getFlattenedErrors() as $key => $errors) {
$parts = explode('.', $key);
$fieldName = array_pop($parts);
$errorsRaw = [];
@@ -52,15 +52,16 @@ class GroupsPriceController extends ActionController
$this->view->assign('hotel', $hotel);
$this->view->assign('runningCostsEUR', $this->settings['runningCostsEUR']);
$this->view->assign('runningCostsCHF', $this->settings['runningCostsCHF']);
$this->view->assign('undersubscriptionEUR', $this->settings['undersubscriptionEUR']);
$this->view->assign('undersubscriptionCHF', $this->settings['undersubscriptionCHF']);
$this->view->assign('undersubscriptionExtEUR', $this->settings['undersubscriptionExtEUR']);
$this->view->assign('undersubscriptionExtCHF', $this->settings['undersubscriptionExtCHF']);
$this->view->assign('undersubscription30EUR', $this->settings['undersubscription30EUR']);
$this->view->assign('undersubscription30CHF', $this->settings['undersubscription30CHF']);
$this->view->assign('undersubscription40EUR', $this->settings['undersubscription40EUR']);
$this->view->assign('undersubscription40CHF', $this->settings['undersubscription40CHF']);
$this->view->assign('countryCode', $hotel->getCountry()->getCode());
$this->view->assign('configs', json_encode(array_values($configs), JSON_HEX_APOS));
$this->view->assign('boards', json_encode($hotel->getGroupsPriceBoards()->toArray(), JSON_HEX_APOS));
$this->view->assign('options', json_encode($hotel->getGroupsPriceOptions()->toArray(), JSON_HEX_APOS));
$this->view->assign('mainSeasonFrom', $mainSeasonFrom);
$this->view->assign('mainSeasonTo', $mainSeasonTo);
$this->view->assign('adolescentsAge', $hotel->getGroupsPriceAdolescentsAge() ?? 0);
}
}
@@ -2,11 +2,10 @@
namespace EP\EpProducts\Controller;
use EP\EpProducts\Domain\Model\Date;
use EP\EpProducts\Domain\Model\Travelinfo;
use EP\EpProducts\Domain\Repository\DateRepository;
use EP\EpProducts\Domain\Repository\ProductRepository;
use EP\EpProducts\Domain\Repository\TravelinfoRepository;
use EP\EpProducts\MyEP\ApiClient;
use EP\EpProducts\MyEP\ApiException;
use TYPO3\CMS\Core\Http\ImmediateResponseException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
@@ -15,59 +14,34 @@ use TYPO3\CMS\Frontend\Controller\ErrorController;
class TravelinfoController extends ActionController
{
/**
* @var DateRepository
* @var ApiClient
*/
private $dateRepository;
private $apiClient;
/**
* @var TravelinfoRepository
*/
private $travelinfoRepository;
/**
* @var ProductRepository
*/
private $productRepository;
public function __construct(
DateRepository $dateRepository,
TravelinfoRepository $travelinfoRepository,
ProductRepository $productRepository
) {
$this->dateRepository = $dateRepository;
public function __construct(ApiClient $apiClient, TravelinfoRepository $travelinfoRepository)
{
$this->apiClient = $apiClient;
$this->travelinfoRepository = $travelinfoRepository;
$this->productRepository = $productRepository;
}
public function travelCodeAction(string $travelCode)
{
$travelCode = strtoupper($travelCode);
$travelCodeDate = str_replace('-', '/', $travelCode);
// find specific travel info by code including travel date first
$travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCode);
/** @var Date $date */
$date = $this->dateRepository->findOneByCode($travelCodeDate);
if (null === $date) {
$response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
$GLOBALS['TYPO3_REQUEST'],
'Termin nicht gefunden'
);
throw new ImmediateResponseException($response, 1731071856);
// find general travel info by code without travel date as fallback
if (null === $travelInfo) {
$travelCodeBase = substr($travelCode, 0, -6);
$travelInfo = $this->travelinfoRepository->findOneByTravelCode($travelCodeBase);
}
$product = $date->getProduct();
if (null === $product) {
$response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
$GLOBALS['TYPO3_REQUEST'],
'Produkt nicht gefunden'
);
throw new ImmediateResponseException($response, 1731071856);
}
$travelinfo = $this->travelinfoRepository->findOneByProduct($product);
if (null === $travelinfo) {
// return 404 in case no travel info is available as last resort
if (null === $travelInfo) {
$response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
$GLOBALS['TYPO3_REQUEST'],
'Reiseinformationen nicht gefunden'
@@ -75,22 +49,49 @@ class TravelinfoController extends ActionController
throw new ImmediateResponseException($response, 1731071856);
}
$this->view->assign('travelinfo', $travelinfo);
$this->view->assign('date', $date);
$this->handleRequest($travelCode, $travelInfo);
}
public function indexAction(Travelinfo $travelinfo, string $travelDate)
public function travelInfoAction(Travelinfo $travelInfo)
{
$product = $travelinfo->getProduct();
$travelCode = $travelInfo->getTravelCode();
if (preg_match('/^\d{2}\d{2}\d{4}$/', $travelDate)) {
$travelDate = (\DateTimeImmutable::createFromFormat('dmY', $travelDate))->format('Y-m-d');
$this->handleRequest($travelCode, $travelInfo);
}
private function handleRequest(string $travelCode, Travelinfo $travelInfo)
{
// fetch travel data via API call
try {
$travelData = $this->apiClient->getTravel($travelCode);
} catch (ApiException $e) {
$travelData = [];
}
/** @var Date $date */
$date = $this->dateRepository->findForProductAndDate($product, $travelDate)->getFirst();
// extract courses from additional services
$courses = array_filter($travelData['additionalServices'] ?? [], function ($service) {
return 'KUR' === $service['subType'];
});
$this->view->assign('travelinfo', $travelinfo);
$this->view->assign('date', $date);
// extract board services from additional services
$board = array_filter($travelData['additionalServices'] ?? [], function ($service) {
return 'VPF' === $service['subType'];
});
// extract bus departure time if applicable
$departureDayTime = null;
$service = array_filter($travelData['transportationServices'] ?? [], function ($service) {
return 'BUS' === $service['subType'] && 'RUECK' === $service['direction'];
});
if (null !== $service[0] ?? null) {
$departureDayTime = $service[0]['dayTime'];
}
$this->view->assign('travelInfo', $travelInfo);
$this->view->assign('travelData', $travelData);
$this->view->assign('courses', $courses);
$this->view->assign('board', $board);
$this->view->assign('departureDayTime', $departureDayTime);
}
}
@@ -60,7 +60,19 @@ class GroupsPriceInquiry
* @var string
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $address;
protected $street;
/**
* @var string
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $postcode;
/**
* @var string
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $city;
/**
* @var string
@@ -92,11 +104,31 @@ class GroupsPriceInquiry
*/
protected $dateTo;
/**
* @var string
*/
protected $nights;
/**
* @var string
*/
protected $pax;
/**
* @var string
*/
protected $children;
/**
* @var string
*/
protected $minors;
/**
* @var string
*/
protected $adolescents;
/**
* @var array
*/
@@ -104,6 +136,7 @@ class GroupsPriceInquiry
/**
* @var GroupsPriceBoard
* @TYPO3\CMS\Extbase\Annotation\Validate("NotEmpty")
*/
protected $board;
@@ -186,21 +219,53 @@ class GroupsPriceInquiry
/**
* @return string
*/
public function getAddress()
public function getStreet()
{
return $this->address;
return $this->street;
}
/**
* @param string $address
* @param string $street
*/
public function setAddress($address)
public function setStreet($street)
{
$this->address = $address;
$this->street = $street;
return $this;
}
/**
* @return string
*/
public function getPostcode()
{
return $this->postcode;
}
/**
* @param string $postcode
*/
public function setPostcode($postcode)
{
$this->postcode = $postcode;
}
/**
* @return string
*/
public function getCity()
{
return $this->city;
}
/**
* @param string $city
*/
public function setCity($city)
{
$this->city = $city;
}
/**
* @return string
*/
@@ -281,6 +346,22 @@ class GroupsPriceInquiry
$this->dateTo = $dateTo;
}
/**
* @return string
*/
public function getNights()
{
return $this->nights;
}
/**
* @param string $nights
*/
public function setNights($nights)
{
$this->nights = $nights;
}
/**
* @return string
*/
@@ -297,6 +378,62 @@ class GroupsPriceInquiry
$this->pax = $pax;
}
/**
* @return string
*/
public function getChildren()
{
return $this->children;
}
/**
* @param string $pax
*/
public function setChildren($children)
{
$this->children = $children;
}
/**
* @return string
*/
public function getMinors()
{
return $this->minors;
}
/**
* @param string $minors
*/
public function setMinors($minors)
{
$this->minors = $minors;
}
/**
* @return string
*/
public function getAdolescents()
{
return $this->adolescents;
}
/**
* @param string $adolescents
*/
public function setAdolescents($adolescents)
{
$this->adolescents = $adolescents;
}
/**
* @return int
*/
public function getEffectivePax()
{
return max(30, $this->getPax() - $this->getChildren());
}
/**
* @return array
*/
@@ -265,6 +265,16 @@ class Hotel extends AbstractEntity implements TeaserInterface
*/
protected $groupsPriceBoards;
/**
* @var int
*/
protected $groupsPriceAdolescentsAge;
/**
* @var string
*/
protected $groupsPriceTaxLabel;
/**
* @var \DateTime
*/
@@ -1054,6 +1064,26 @@ class Hotel extends AbstractEntity implements TeaserInterface
$this->groupsPriceBoards = $groupsPriceBoards;
}
public function getGroupsPriceAdolescentsAge()
{
return $this->groupsPriceAdolescentsAge;
}
public function setGroupsPriceAdolescentsAge($groupsPriceAdolescentsAge)
{
$this->groupsPriceAdolescentsAge = $groupsPriceAdolescentsAge;
}
public function getGroupsPriceTaxLabel()
{
return $this->groupsPriceTaxLabel;
}
public function setGroupsPriceTaxLabel($groupsPriceTaxLabel)
{
$this->groupsPriceTaxLabel = $groupsPriceTaxLabel;
}
public function getMainSeasonFrom()
{
return $this->mainSeasonFrom;
@@ -191,7 +191,7 @@ class ProductRepository extends AbstractRepository
;
}
public function getPricetable(Product $product, Hotel $hotel, array $filterSettings, Date $date = null, bool $sortByRoom = true): array
public function getPricetable(Product $product, Hotel $hotel, array $filterSettings, Date $date = null): array
{
$qb = $this->getDbConnection()->createQueryBuilder();
@@ -207,18 +207,11 @@ class ProductRepository extends AbstractRepository
)
->from('tx_epproducts_domain_model_date', 'date')
->innerJoin('date', 'tx_epproducts_domain_model_room', 'room', 'date.uid = room.date')
->orderBy('room.available', 'DESC')
->orderBy('room.pax', 'ASC')
->addOrderBy('room.price', 'ASC')
->addOrderBy('room.available', 'DESC')
;
if (true === $sortByRoom) {
$qb->addOrderBy('room.name', 'ASC');
} else {
$qb
->addOrderBy('room.pax', 'ASC')
->addOrderBy('room.price', 'ASC')
;
}
$qb
->where('date.date_start >= NOW()')
->andWhere($qb->expr()->eq(
@@ -365,7 +358,8 @@ class ProductRepository extends AbstractRepository
'date.bus_pro_id as dateBusProId', 'date.hotel_bus_pro_id as hotelBusProId',
'date.date_end as dateEnd', 'date.hotel as hotelUid', 'date.hotel_name as hotelName',
'date.hotel_header_title as hotelTitle', 'date.hotel_category as hotelCategory',
'room.pax as roomPax', 'room.price as roomPrice', 'room.available as available',
'room.name as roomName', 'room.pax as roomPax', 'room.price as roomPrice',
'room.available as available',
'hotel.detail_page as hotelDetailPageUid', 'hotel.short_name as hotelShortName',
'hotel.show_as_teaser as hotelShowAsTeaser'
)
@@ -0,0 +1,149 @@
<?php
namespace EP\EpProducts\MyEP;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\GenericProvider;
use League\OAuth2\Client\Token\AccessToken;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class ApiClient
{
private static ?AccessToken $accessToken = null;
/**
* @throws ApiException
*/
public function getLastUpdateAt(): ?\DateTimeImmutable
{
$httpClient = $this->getHttpClient();
try {
$response = $httpClient->request('GET', 'last-update');
} catch (TransportExceptionInterface $e) {
throw new ApiException($e->getMessage());
}
try {
if (200 !== $response->getStatusCode()) {
throw new ApiException($response->getStatusCode());
}
$data = $response->toArray(false);
if (null === $data['timestamp'] ?? null) {
return null;
}
return new \DateTimeImmutable($data['timestamp']);
} catch (\Throwable $e) {
throw new ApiException($e->getMessage());
}
}
/**
* @throws ApiException
*/
public function getPickups(): array
{
$httpClient = $this->getHttpClient();
try {
$response = $httpClient->request('GET', 'pickups');
} catch (TransportExceptionInterface $e) {
throw new ApiException($e->getMessage());
}
try {
if (200 !== $response->getStatusCode()) {
throw new ApiException($response->getStatusCode());
}
return $response->toArray(false);
} catch (\Throwable $e) {
throw new ApiException($e->getMessage());
}
}
/**
* @throws ApiException
*/
public function getTravels(): array
{
$httpClient = $this->getHttpClient();
try {
$response = $httpClient->request('GET', 'travels');
} catch (TransportExceptionInterface $e) {
throw new ApiException($e->getMessage());
}
try {
if (200 !== $response->getStatusCode()) {
throw new ApiException($response->getStatusCode());
}
return $response->toArray(false);
} catch (\Throwable $e) {
throw new ApiException($e->getMessage());
}
}
/**
* @throws ApiException
*/
public function getTravel(string $productCode): array
{
$httpClient = $this->getHttpClient();
try {
$response = $httpClient->request('GET', 'travels/' . $productCode);
} catch (TransportExceptionInterface $e) {
throw new ApiException($e->getMessage());
}
try {
if (200 !== $response->getStatusCode()) {
throw new ApiException($response->getStatusCode());
}
return $response->toArray(false);
} catch (\Throwable $e) {
throw new ApiException($e->getMessage());
}
}
private function getHttpClient(): HttpClientInterface
{
$config = GeneralUtility::makeInstance(ExtensionConfiguration::class)
->get('ep_products');
if (null === static::$accessToken || true === static::$accessToken->hasExpired()) {
static::$accessToken = $this
->getProvider($config)
->getAccessToken('client_credentials')
;
}
return HttpClient::createForBaseUri($config['myEpApiBaseUrl'], [
'auth_bearer' => static::$accessToken->getToken(),
]);
}
private function getProvider(array $config): AbstractProvider
{
return new GenericProvider([
'clientId' => $config['myEpApiClientId'],
'clientSecret' => $config['myEpApiClientSecret'],
'redirectUri' => null,
'urlAuthorize' => $config['myEpApiUrlAuthorize'],
'urlAccessToken' => $config['myEpApiUrlToken'],
'urlResourceOwnerDetails' => null,
'scopes' => 'api',
]);
}
}
@@ -0,0 +1,7 @@
<?php
namespace EP\EpProducts\MyEP;
class ApiException extends \Exception
{
}
@@ -109,7 +109,6 @@ class DateService implements SingletonInterface
$date = $resolvedOptions['date'];
$template = $resolvedOptions['template'];
$paCode = $resolvedOptions['paCode'];
$sortByRoom = $resolvedOptions['sortByRoom'];
if ($filterSettings === null) {
$filterSettings = $this->filterService->getNormalizedFilterSettings();
@@ -118,7 +117,7 @@ class DateService implements SingletonInterface
$nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true);
return $this->preprocessPriceTable([
'priceTableData' => $this->productRepository->getPricetable($product, $hotel, $filterSettings, $date, $sortByRoom),
'priceTableData' => $this->productRepository->getPricetable($product, $hotel, $filterSettings, $date),
'template' => $template,
'paCode' => $paCode,
'isDayTrip' => $product->isDaytrip(),
@@ -188,7 +187,11 @@ class DateService implements SingletonInterface
$resolver->setDefault('isDayTrip', false);
$resolver->setDefault('showBookingButton', true);
$resolvedOptions = $resolver->resolve($options);
$priceTable = [];
$priceTable = [
'available' => [],
'unavailable' => [],
];
$priceTableData = $resolvedOptions['priceTableData'];
$template = $resolvedOptions['template'];
@@ -210,7 +213,10 @@ class DateService implements SingletonInterface
'isDayTrip' => $isDayTrip,
]);
$optionalServices = $this->preprocessOptionalServices(unserialize($row['roomOptionalServices'], ['allowed_classes' => false]));
$priceTable[] = [
$key = $row['roomAvailable'] && $row['roomPrice'] ? 'available' : 'unavailable';
$priceTable[$key][] = [
'dateStart' => $dateStart->format('d.m.Y'),
'dateEnd' => $dateEnd->format('d.m.Y'),
'season' => $row['season'],
@@ -378,6 +384,8 @@ class DateService implements SingletonInterface
}
$entry =& $priceTable[$categoryUid][$hotelUid]['rooms'];
$isUndersubscription = 1 === preg_match('/mit \d{1,2} Personen/', $row['roomName']);
// Add new entry or replace possible existing entry that is not
// bookable with current record in case of matching category
// and pax
@@ -385,7 +393,7 @@ class DateService implements SingletonInterface
$entry[$roomPax] = [
'price' => $row['roomPrice'],
'available' => $row['available'] && $row['roomPrice'],
'undersubscription' => $isUndersubscription,
] ;
} else {
$lastEntry = $entry[$roomPax];
@@ -393,6 +401,7 @@ class DateService implements SingletonInterface
$entry[$roomPax] = [
'price' => $row['roomPrice'],
'available' => $row['available'] && $row['roomPrice'],
'undersubscription' => $isUndersubscription,
];
}
}
@@ -69,7 +69,7 @@ class SearchResultService implements SingletonInterface
'destinationUid' => null,
'destinationType' => null,
'nights' => null,
'pax' => 1,
'pax' => 0,
'pageUid' => null,
]);
@@ -32,7 +32,7 @@
</config>
</TCEforms>
</settings.runningCostsCHF>
<settings.undersubscriptionEUR>
<settings.undersubscription30EUR>
<TCEforms>
<exclude>0</exclude>
<label>Unterbelegung 30-39 Personen EUR</label>
@@ -42,8 +42,8 @@
<eval>double2,required</eval>
</config>
</TCEforms>
</settings.undersubscriptionEUR>
<settings.undersubscriptionCHF>
</settings.undersubscription30EUR>
<settings.undersubscription30CHF>
<TCEforms>
<exclude>0</exclude>
<label>Unterbelegung 30-39 Personen CHF</label>
@@ -53,8 +53,8 @@
<eval>double2,required</eval>
</config>
</TCEforms>
</settings.undersubscriptionCHF>
<settings.undersubscriptionExtEUR>
</settings.undersubscription30CHF>
<settings.undersubscription40EUR>
<TCEforms>
<exclude>0</exclude>
<label>Unterbelegung 40-49 Personen EUR</label>
@@ -64,8 +64,8 @@
<eval>double2,required</eval>
</config>
</TCEforms>
</settings.undersubscriptionExtEUR>
<settings.undersubscriptionExtCHF>
</settings.undersubscription40EUR>
<settings.undersubscription40CHF>
<TCEforms>
<exclude>0</exclude>
<label>Unterbelegung 40-49 Personen CHF</label>
@@ -75,7 +75,7 @@
<eval>double2,required</eval>
</config>
</TCEforms>
</settings.undersubscriptionExtCHF>
</settings.undersubscription40CHF>
</el>
</ROOT>
</sDEF>
@@ -44,6 +44,7 @@ return [
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1],
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0]
],
'default' => -1,
],
],
'l10n_parent' => [
@@ -45,6 +45,7 @@ return [
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.allLanguages', -1],
['LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.default_value', 0]
],
'default' => -1,
],
],
'l10n_parent' => [
@@ -30,7 +30,8 @@ return [
--div--;Texte, teaser, description, features, room_types, additional_information,
--div--;Verknüpfungen, facts, teamer, additional_codes,
--div--;Bilder/Dateien, teaser_images, header_images, images, reseller_images,
--div--;Gruppenhaus Preise, --palette--;;season, groups_price_configs, groups_price_boards, groups_price_options',
--div--;Gruppenhaus Preise, --palette--;;season,--palette--;;others, groups_price_configs,
groups_price_boards, groups_price_options',
],
],
'palettes' => [
@@ -41,6 +42,7 @@ return [
'name' => ['showitem' => 'name,short_name,--linebreak--,header_title,header_subtitle,--linebreak--,headline'],
'categories' => ['showitem' => 'type,category'],
'season' => ['showitem' => 'main_season_from, main_season_to'],
'others' => ['showitem' => 'season,groups_price_tax_label, groups_price_adolescents_age']
],
'columns' => [
@@ -639,6 +641,7 @@ return [
'type' => 'inline',
'foreign_table' => 'tx_epproducts_domain_model_groupspriceconfig',
'foreign_field' => 'hotel',
'foreign_sortby' => 'sorting',
'maxitems' => 99,
'minitems' => 0,
'appearance' => [
@@ -691,6 +694,25 @@ return [
],
],
],
'groups_price_tax_label' => [
'exclude' => 0,
'label' => 'Text Ortstaxe',
'config' => [
'type' => 'input',
'size' => 50,
'eval' => 'trim',
'default' => 'zzgl. vor Ort zu entrichtender Ortstaxe',
],
],
'groups_price_adolescents_age' => [
'exclude' => 0,
'label' => 'Altersgrenze Jugendliche (6-X Jahre)',
'config' => [
'type' => 'input',
'size' => 50,
'eval' => 'int',
],
],
'main_season_from' => [
'exclude' => false,
'label' => 'Hauptsaison von',
@@ -27,7 +27,7 @@ return [
],
],
'palettes' => [
'basics' => ['showitem' => 'title,path_segment,--linebreak--,product,hide_address,--linebreak--,links,
'basics' => ['showitem' => 'title,path_segment,--linebreak--,travel_code,hide_address,--linebreak--,links,
--linebreak--,teasers'],
'text' => ['showitem' => 'subline,headline,--linebreak--,intro_text,--linebreak--, bus_info_text,--linebreak--,
departure_text,--linebreak--,footer_text,--linebreak--,self_arranged_text,--linebreak--,additional_info,
@@ -191,32 +191,13 @@ return [
$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext']
),
],
'product' => [
'travel_code' => [
'exclude' => false,
'label' => 'Produkt',
'label' => 'Reisecode',
'config' => [
'type' => 'group',
'internal_type' => 'db',
'allowed' => 'tx_epproducts_domain_model_product',
'minitems' => 1,
'maxitems' => 1,
'size' => 1,
'fieldControl' => [
'elementBrowser' => [
'disabled' => true,
],
],
'fieldWizard' => [
'recordsOverview' => [
'disabled' => true,
],
],
'suggestOptions' => [
'default' => [
'additionalSearchFields' => 'code,name,keywords',
'orderBy' => 'code',
],
],
'type' => 'input',
'size' => 30,
'eval' => 'trim,required'
],
],
'hide_address' => [
@@ -1,3 +1,18 @@
# customsubcategory=100=General
# cat=epproducts/100/100; type=int; label=Searchresult PID
# cat=General; type=int; label=Searchresult PID
searchPageUid = 0
# cat=MyEpAPI; type=string; label=MyE&P API base URL (trailing slash!)
myEpApiBaseUrl =
# cat=MyEpAPI; type=string; label=MyE&P API client id
myEpApiClientId =
# cat=MyEpAPI; type=string; label=MyE&P API client secret
myEpApiClientSecret =
# cat=MyEpAPI; type=string; label=MyE&P API authorization URL
myEpApiUrlAuthorize =
# cat=MyEpAPI; type=string; label=MyE&P API token URL
myEpApiUrlToken =
@@ -252,7 +252,7 @@ $boot = function () {
'EP.ep_products',
'travelinfo',
[
'Travelinfo' => 'index,travelCode',
'Travelinfo' => 'travelInfo,travelCode',
]
);
+64 -62
View File
@@ -213,72 +213,74 @@ CREATE TABLE tx_epproducts_domain_model_date
CREATE TABLE tx_epproducts_domain_model_hotel
(
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
earlybird tinyint(4) unsigned DEFAULT '0' NOT NULL,
new tinyint(4) unsigned DEFAULT '0' NOT NULL,
low_contingent tinyint(4) unsigned DEFAULT '0' NOT NULL,
show_as_teaser tinyint(4) unsigned DEFAULT '0' NOT NULL,
detail_page varchar(255) DEFAULT '' NOT NULL,
name varchar(255) DEFAULT '' NOT NULL,
short_name varchar(255) DEFAULT '' NOT NULL,
headline varchar(255) DEFAULT '' NOT NULL,
description text NOT NULL,
keywords text NOT NULL,
features text NOT NULL,
room_types text NOT NULL,
additional_information text NOT NULL,
teaser text NOT NULL,
header_title varchar(255) DEFAULT '' NOT NULL,
header_subtitle varchar(255) DEFAULT '' NOT NULL,
teaser_images int(11) unsigned NOT NULL default '0',
header_images int(11) unsigned NOT NULL default '0',
images int(11) unsigned NOT NULL default '0',
reseller_images int(11) unsigned NOT NULL default '0',
code varchar(255) DEFAULT '' NOT NULL,
external_link varchar(255) DEFAULT '' NOT NULL,
path_segment varchar(255) DEFAULT '' NOT NULL,
type varchar(255) DEFAULT '' NOT NULL,
category int(11) DEFAULT '0' NOT NULL,
country int(11) unsigned DEFAULT '0',
region int(11) unsigned DEFAULT '0',
city int(11) unsigned DEFAULT '0',
teamer int(11) unsigned DEFAULT '0' NOT NULL,
products int(11) unsigned DEFAULT '0' NOT NULL,
rooms int(11) unsigned DEFAULT '0' NOT NULL,
facts int(11) unsigned DEFAULT '0' NOT NULL,
additional_codes int(11) unsigned DEFAULT '0' NOT NULL,
address varchar(255) DEFAULT '' NOT NULL,
latitude varchar(255) DEFAULT '' NOT NULL,
longitude varchar(255) DEFAULT '' NOT NULL,
groups_price_configs int(11) unsigned NOT NULL default '0',
groups_price_options int(11) unsigned NOT NULL default '0',
groups_price_boards int(11) unsigned NOT NULL default '0',
main_season_from date DEFAULT NULL,
main_season_to date DEFAULT NULL,
earlybird tinyint(4) unsigned DEFAULT '0' NOT NULL,
new tinyint(4) unsigned DEFAULT '0' NOT NULL,
low_contingent tinyint(4) unsigned DEFAULT '0' NOT NULL,
show_as_teaser tinyint(4) unsigned DEFAULT '0' NOT NULL,
detail_page varchar(255) DEFAULT '' NOT NULL,
name varchar(255) DEFAULT '' NOT NULL,
short_name varchar(255) DEFAULT '' NOT NULL,
headline varchar(255) DEFAULT '' NOT NULL,
description text NOT NULL,
keywords text NOT NULL,
features text NOT NULL,
room_types text NOT NULL,
additional_information text NOT NULL,
teaser text NOT NULL,
header_title varchar(255) DEFAULT '' NOT NULL,
header_subtitle varchar(255) DEFAULT '' NOT NULL,
teaser_images int(11) unsigned NOT NULL default '0',
header_images int(11) unsigned NOT NULL default '0',
images int(11) unsigned NOT NULL default '0',
reseller_images int(11) unsigned NOT NULL default '0',
code varchar(255) DEFAULT '' NOT NULL,
external_link varchar(255) DEFAULT '' NOT NULL,
path_segment varchar(255) DEFAULT '' NOT NULL,
type varchar(255) DEFAULT '' NOT NULL,
category int(11) DEFAULT '0' NOT NULL,
country int(11) unsigned DEFAULT '0',
region int(11) unsigned DEFAULT '0',
city int(11) unsigned DEFAULT '0',
teamer int(11) unsigned DEFAULT '0' NOT NULL,
products int(11) unsigned DEFAULT '0' NOT NULL,
rooms int(11) unsigned DEFAULT '0' NOT NULL,
facts int(11) unsigned DEFAULT '0' NOT NULL,
additional_codes int(11) unsigned DEFAULT '0' NOT NULL,
address varchar(255) DEFAULT '' NOT NULL,
latitude varchar(255) DEFAULT '' NOT NULL,
longitude varchar(255) DEFAULT '' NOT NULL,
groups_price_configs int(11) unsigned NOT NULL default '0',
groups_price_options int(11) unsigned NOT NULL default '0',
groups_price_boards int(11) unsigned NOT NULL default '0',
groups_price_adolescents_age int(11) unsigned NOT NULL default '0',
groups_price_tax_label varchar(255) DEFAULT '' NOT NULL,
main_season_from date DEFAULT NULL,
main_season_to date DEFAULT NULL,
tstamp int(11) unsigned DEFAULT '0' NOT NULL,
crdate int(11) unsigned DEFAULT '0' NOT NULL,
cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
deleted tinyint(4) unsigned DEFAULT '0' NOT NULL,
hidden tinyint(4) unsigned DEFAULT '0' NOT NULL,
starttime int(11) unsigned DEFAULT '0' NOT NULL,
endtime int(11) unsigned DEFAULT '0' NOT NULL,
tstamp int(11) unsigned DEFAULT '0' NOT NULL,
crdate int(11) unsigned DEFAULT '0' NOT NULL,
cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
deleted tinyint(4) unsigned DEFAULT '0' NOT NULL,
hidden tinyint(4) unsigned DEFAULT '0' NOT NULL,
starttime int(11) unsigned DEFAULT '0' NOT NULL,
endtime int(11) unsigned DEFAULT '0' NOT NULL,
t3ver_oid int(11) DEFAULT '0' NOT NULL,
t3ver_id int(11) DEFAULT '0' NOT NULL,
t3ver_wsid int(11) DEFAULT '0' NOT NULL,
t3ver_label varchar(255) DEFAULT '' NOT NULL,
t3ver_state tinyint(4) DEFAULT '0' NOT NULL,
t3ver_stage int(11) DEFAULT '0' NOT NULL,
t3ver_count int(11) DEFAULT '0' NOT NULL,
t3ver_tstamp int(11) DEFAULT '0' NOT NULL,
t3ver_move_id int(11) DEFAULT '0' NOT NULL,
t3ver_oid int(11) DEFAULT '0' NOT NULL,
t3ver_id int(11) DEFAULT '0' NOT NULL,
t3ver_wsid int(11) DEFAULT '0' NOT NULL,
t3ver_label varchar(255) DEFAULT '' NOT NULL,
t3ver_state tinyint(4) DEFAULT '0' NOT NULL,
t3ver_stage int(11) DEFAULT '0' NOT NULL,
t3ver_count int(11) DEFAULT '0' NOT NULL,
t3ver_tstamp int(11) DEFAULT '0' NOT NULL,
t3ver_move_id int(11) DEFAULT '0' NOT NULL,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
sys_language_uid int(11) DEFAULT '0' NOT NULL,
l10n_parent int(11) DEFAULT '0' NOT NULL,
l10n_diffsource mediumblob,
PRIMARY KEY (uid),
KEY parent (pid),
@@ -29,6 +29,7 @@ class IconViewHelper extends AbstractTagBasedViewHelper
$content = sprintf('<use href="%s#icon-%s"></use>', $imageUrl, $this->arguments['icon']);
$this->tag->setContent($content);
$this->tag->addAttributes(['aria-hidden' => 'true']);
return $this->tag->render();
}
@@ -41,6 +41,8 @@ class LazyLoadImageViewHelper extends ImageViewHelper
$classItems[] = 'lazyload';
$classes = implode(' ', array_unique($classItems));
$this->tag->addAttribute('class', $classes);
// ensure a11y compliance
$this->tag->removeAttribute('title');
return $this->tag->render();
}
@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<!-- Symbols -->
<symbol id="icon-menu" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></symbol>
<symbol id="icon-chevron-down" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></symbol>

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

@@ -16,20 +16,76 @@
Einzelne Nächte sind nur auf Anfrage buchbar
</p>
</div>
<div class="mb-8"
<div class="mb-8 grid sm:grid-cols-2 gap-8"
v-show="!submitted">
<label class="mb-2 font-bold">
Personenzahl
</label>
<input type="number"
class="form-field"
v-model.number="selectedPax"
@change="onPaxUpdated()"
v-show="nightsCount > 0"/>
<p class="text-sm italic"
v-show="nightsCount === 0">
Bitte zuerst einen Zeitraum wählen
</p>
<div>
<label class="mb-2 font-bold">
Personenzahl
</label>
<input type="number"
class="form-field"
min="30"
v-model.number="selectedPax"
@change="onPaxUpdated()"
v-show="nightsCount > 0"/>
<p class="text-sm italic"
v-show="nightsCount === 0">
Bitte zuerst einen Zeitraum wählen
</p>
</div>
<div>
<label class="mb-2 font-bold">
davon Kinder 0-3 Jahre
</label>
<input type="number"
class="form-field"
min="0"
v-model.number="childrenCount"
@change="onPaxUpdated()"
v-show="nightsCount > 0"/>
<p class="text-sm italic"
v-show="nightsCount === 0">
Bitte zuerst einen Zeitraum wählen
</p>
<p class="text-sm italic"
v-show="nightsCount > 0">
Kinder werden nur bei Strom- und Abfallgebühren berücksichtigt.
</p>
</div>
<div>
<label class="mb-2 font-bold">
davon Kinder 4-5 Jahre*
</label>
<input type="number"
class="form-field"
min="0"
v-model.number="minorsCount"
@change="onPaxUpdated()"
v-show="nightsCount > 0"/>
<p class="text-sm italic"
v-show="nightsCount === 0">
Bitte zuerst einen Zeitraum wählen
</p>
</div>
<div v-if="adolescentsAge > 0">
<label class="mb-2 font-bold">
davon Kinder 6-{{ adolescentsAge }} Jahre*
</label>
<input type="number"
class="form-field"
min="0"
v-model.number="adolescentsCount"
@change="onPaxUpdated()"
v-show="nightsCount > 0"/>
<p class="text-sm italic"
v-show="nightsCount === 0">
Bitte zuerst einen Zeitraum wählen
</p>
</div>
<div class="sm:col-span-2 text-sm">
*Die Kosten für Essen und Kurtaxe werden wir entsprechend der Altersstruktur der Gäste in der Rechnung
anpassen.
</div>
</div>
<div class="mb-8"
v-show="options.length > 0 && !submitted">
@@ -51,27 +107,38 @@
<div class="mb-8"
v-show="boards.length > 0 && !submitted">
<label class="mb-2 font-bold">
Optionale Verpflegungsleistungen
Verpflegungsleistungen
</label>
<select class="form-field"
v-model="selectedBoard"
v-if="selectedPax >= 30">
<option :value="null">Keine Auswahl</option>
<option :value="null" disabled>bitte auswählen</option>
<option v-for="board of boards"
:value="board"
:key="board.uid">
{{ board.title }} ({{ formatCurrency(board.price) }} pro Person und Nacht)
</option>
</select>
<div class="text-sm text-red-600 mt-1"
v-show="formErrors.board"
v-html="formErrors.board"></div>
<div v-show="selectedPax < 30">
<span class="text-red-600">Erst ab 30 Personen buchbar.</span>
</div>
</div>
<div class="mb-8">
<div class="pb-2" v-if="submitted">
<p class="text-lg font-bold pb-2">
Vielen Dank für Deine {{ mode === 'booking' ? 'Buchung' : 'Anfrage' }}.
</p>
<p>
Für folgendes habt ihr euch entschieden:
</p>
</div>
<div class="p-2 uppercase bg-ep-primary-dark text-white w-full">
Preise
</div>
<div class="bg-zinc-100 p-2">
<div class="bg-zinc-100 p-2 mb-2">
<table class="w-full" v-show="nightsCount > 0">
<tbody>
<tr class="odd:bg-zinc-100">
@@ -95,7 +162,7 @@
<tr class="odd:bg-zinc-100"
v-for="selectedOption in selectedOptions">
<th class="text-left py-2">{{ selectedOption.title }}</th>
<td>{{ formatCurrency(selectedOption.price) }}</td>
<td>{{ formatCurrency(calculateOptionPrice(selectedOption)) }}</td>
</tr>
<tr class="odd:bg-zinc-100"
v-if="priceBoard.EUR > 0 || priceBoard.CHF > 0">
@@ -103,8 +170,8 @@
<td>{{ formatCurrency(priceBoard) }}</td>
</tr>
<tr class="odd:bg-zinc-100"
v-if="pricePax.undersubscription.EUR > 0 || pricePax.undersubscription.CHF > 0">
<th class="text-left py-2">Kleingruppen-Verpflegungszuschlag</th>
v-if="(priceBoard.EUR > 0 || priceBoard.CHF > 0) && (pricePax.undersubscription.EUR > 0 || pricePax.undersubscription.CHF > 0)">
<th class="text-left py-2">Verpflegungs-Aufschlag für Gruppen unter 50 Personen</th>
<td>{{ formatCurrency(pricePax.undersubscription) }}</td>
</tr>
<tr class="odd:bg-zinc-100"
@@ -114,8 +181,10 @@
</tr>
<tr class="odd:bg-zinc-100">
<th class="text-left py-2">Gesamtpreis</th>
<td><strong>{{ formatCurrency(priceTotal) }}</strong>
<br><small>zzgl. vor Ort zu entrichtender Ortstaxe</small>
<td>
<strong>{{ formatCurrency(priceTotal) }}</strong>
<br>
<small>{{ taxLabel }}</small>
</td>
</tr>
</tbody>
@@ -125,6 +194,15 @@
Bitte zuerst einen Zeitraum wählen
</p>
</div>
<div v-if="mode === 'booking' && submitted">
Bitte beachtet, dass die Buchung bei uns eingegangen, aber <strong>noch nicht bestätigt</strong> ist.
Wir behalten uns vor, die Buchungen auf Verfügbarkeit und Länge des gewünschten Aufenthalts zu prüfen.
Erst wenn wir euch die Buchung per Mail bestätigt haben, wird sie bindend.
</div>
<div v-if="mode === 'inquiry' && submitted">
Die Anfrage ist bei uns eingegangen und wird schnellstmöglich bearbeitet. Wir melden uns telefonisch
oder per Mail bei euch.
</div>
</div>
<div class="mb-8"
v-show="nightsCount > 0 && !submitted">
@@ -144,17 +222,38 @@
v-show="formErrors.name"
v-html="formErrors.name"></div>
</div>
<div :class="{ 'has-error': formErrors.address }">
<div :class="{ 'has-error': formErrors.street }">
<label class="font-bold mb-2">
Adresse*
Strasse, Nr.*
</label>
<textarea class="form-field"
cols="30"
rows="3"
v-model="address"/>
<input type="text"
class="form-field"
v-model="street">
<div class="text-sm text-red-600 mt-1"
v-show="formErrors.address"
v-html="formErrors.address"></div>
v-show="formErrors.street"
v-html="formErrors.street"></div>
</div>
<div :class="{ 'has-error': formErrors.postcode }">
<label class="font-bold mb-2">
Postleitzahl*
</label>
<input type="text"
class="form-field"
v-model="postcode">
<div class="text-sm text-red-600 mt-1"
v-show="formErrors.postcode"
v-html="formErrors.postcode"></div>
</div>
<div :class="{ 'has-error': formErrors.city }">
<label class="font-bold mb-2">
Ort*
</label>
<input type="text"
class="form-field"
v-model="city">
<div class="text-sm text-red-600 mt-1"
v-show="formErrors.city"
v-html="formErrors.city"></div>
</div>
<div :class="{ 'has-error': formErrors.group }">
<label class="font-bold mb-2">
@@ -203,7 +302,7 @@
<input type="checkbox" v-model="confirmation" name="confirmation" id="confirmation">
<span class="block ml-2">
Durch Anklicken des Buttons 'Buchung abschicken' bestätige ich, dass ich die
<a href="https://www.ep-reisen.de/reisen-fuer-gruppen/infos-zusatzleistungen/reiseinfos/agbs/"
<a :href="termsUrls[countryCode]"
class="text-ep-primary" target="_blank">
Allgemeinen Geschäftsbedingungen (AGB)
</a> gelesen habe und damit einverstanden bin, dass eine
@@ -235,9 +334,6 @@
</div>
</div>
</div>
<div class="py-8 text-xl text-center font-bold" v-show="submitted">
Vielen Dank für Deine {{ mode === 'booking' ? 'Buchung' : 'Anfrage' }}. Wir werden diese schnellstmöglich bearbeiten.
</div>
<div>
<img :src="logoAt" alt="" v-if="countryCode === 'AT'" class="block w-full h-auto max-w-24">
<img :src="logoCh" alt="" v-if="countryCode === 'CH'" class="block w-full h-auto max-w-24">
@@ -292,19 +388,19 @@ export default {
type: Number,
default: 2.9,
},
undersubscriptionEur: {
undersubscription30Eur: {
type: Number,
default: 5.0,
},
undersubscriptionChf: {
undersubscription30Chf: {
type: Number,
default: 5.0,
},
undersubscriptionExtEur: {
undersubscription40Eur: {
type: Number,
default: 2.5,
},
undersubscriptionExtChf: {
undersubscription40Chf: {
type: Number,
default: 2.5,
},
@@ -334,6 +430,14 @@ export default {
type: Number,
default: 5,
},
taxLabel: {
type: String,
default: 'zzgl. vor Ort zu entrichtender Ortstaxe',
},
adolescentsAge: {
type: Number,
default: 0,
}
},
data() {
return {
@@ -348,16 +452,26 @@ export default {
selectedTo: null,
selectedRange: [],
selectedPax: 30,
childrenCount: 0,
minorsCount: 0,
adolescentsCount: 0,
selectedOptions: [],
selectedBoard: null,
name: null,
email: null,
phone: null,
remarks: null,
group: null,
address: null,
name: '',
email: '',
phone: '',
remarks: '',
group: '',
street: '',
postcode: '',
city: '',
mode: 'booking',
confirmation: false,
termsUrls: {
AT: 'https://www.ep-reisen.de/fileadmin/user_upload/allgemein/AGB_Gruppen/AGB_Gruppen_CLLT_Touristik_GmbH.pdf',
CH: 'https://www.ep-reisen.de/fileadmin/user_upload/allgemein/AGB_Gruppen/AVB_Gruppen_AlpineVacation_GmbH.pdf',
IT: 'https://www.ep-reisen.de/fileadmin/user_upload/allgemein/AGB_Gruppen/AGB_Gruppen_E_P_Reisen.pdf',
}
}
},
methods: {
@@ -380,6 +494,28 @@ export default {
return price
},
calculateOptionPrice(option) {
let price = {
EUR: 0,
CHF: 0,
};
let optionPriceEUR = option.price.EUR
let optionPriceCHF = option.price.CHF
if (1 === option.type) {
price.EUR = optionPriceEUR;
price.CHF = optionPriceCHF;
} else if (2 === option.type) {
price.EUR = optionPriceEUR * this.paxCount;
price.CHF = optionPriceCHF * this.paxCount;
} else if (3 === option.type) {
price.EUR = optionPriceEUR * this.nightsCount;
price.CHF = optionPriceCHF * this.nightsCount;
} else if (4 === option.type) {
price.EUR = optionPriceEUR * this.nightsCount * this.paxCount;
price.CHF = optionPriceCHF * this.nightsCount * this.paxCount;
}
return price;
},
formatOptionPriceType(option) {
if (2 === option.type) {
return ` (${this.formatCurrency(option.price)} pro Person)`
@@ -431,13 +567,19 @@ export default {
'tx_epproducts_ajax[groupsPriceInquiry][confirmation]': confirmation ? '1' : '0',
'tx_epproducts_ajax[groupsPriceInquiry][name]': this.name ? this.name : '',
'tx_epproducts_ajax[groupsPriceInquiry][group]': this.group ? this.group : '',
'tx_epproducts_ajax[groupsPriceInquiry][address]': this.address ? this.address : '',
'tx_epproducts_ajax[groupsPriceInquiry][street]': this.street ? this.street : '',
'tx_epproducts_ajax[groupsPriceInquiry][postcode]': this.postcode ? this.postcode : '',
'tx_epproducts_ajax[groupsPriceInquiry][city]': this.city ? this.city : '',
'tx_epproducts_ajax[groupsPriceInquiry][email]': this.email? this.email : '',
'tx_epproducts_ajax[groupsPriceInquiry][phone]': this.phone ? this.phone : '',
'tx_epproducts_ajax[groupsPriceInquiry][remarks]': this.remarks,
'tx_epproducts_ajax[groupsPriceInquiry][dateFrom]': this.selectedFrom.format('DD.MM.YYYY'),
'tx_epproducts_ajax[groupsPriceInquiry][dateTo]': this.selectedTo.format('DD.MM.YYYY'),
'tx_epproducts_ajax[groupsPriceInquiry][nights]': this.nightsCount,
'tx_epproducts_ajax[groupsPriceInquiry][pax]': this.selectedPax,
'tx_epproducts_ajax[groupsPriceInquiry][children]': this.childrenCount,
'tx_epproducts_ajax[groupsPriceInquiry][minors]': this.minorsCount,
'tx_epproducts_ajax[groupsPriceInquiry][adolescents]': this.adolescentsCount,
'tx_epproducts_ajax[groupsPriceInquiry][summary][paxBase]': this.formatCurrency(this.pricePax.base),
'tx_epproducts_ajax[groupsPriceInquiry][summary][paxAdditional]': this.formatCurrency(this.pricePax.additional),
'tx_epproducts_ajax[groupsPriceInquiry][summary][undersubscription]': this.formatCurrency(this.pricePax.undersubscription),
@@ -474,6 +616,21 @@ export default {
},
},
computed: {
isSelfCatering() {
let selectedBoard = this.selectedBoard
if (null === selectedBoard) {
return false
}
if ('CH' === this.countryCode) {
return selectedBoard.price.CHF === 0
} else {
return selectedBoard.price.EUR === 0
}
},
paxCount() {
// subtract number of children from pax for price calculation, but minimum 30 pax
return Math.max(this.selectedPax - this.childrenCount, 30)
},
nightsCount() {
return this.selectedRange.length;
},
@@ -506,20 +663,20 @@ export default {
base.EUR += config.price.EUR
base.CHF += config.price.CHF
let included = config.personsIncluded
let additionalPax = this.selectedPax - included
if (this.selectedPax > included) {
if (this.paxCount > included) {
let additionalPax = this.paxCount - included
additional.EUR += additionalPax * config.priceAdditionalPerson.EUR
additional.CHF += additionalPax * config.priceAdditionalPerson.CHF
}
}
}
}
if (this.selectedBoard && this.selectedPax < 40) {
undersubscription.EUR = this.selectedPax * this.undersubscriptionEur * this.selectedRange.length
undersubscription.CHF = this.selectedPax * this.undersubscriptionChf * this.selectedRange.length
} else if (this.selectedBoard && this.selectedPax < 50) {
undersubscription.EUR = this.selectedPax * this.undersubscriptionExtEur * this.selectedRange.length
undersubscription.CHF = this.selectedPax * this.undersubscriptionExtChf * this.selectedRange.length
if (false === this.isSelfCatering && this.paxCount < 40) {
undersubscription.EUR = this.paxCount * this.undersubscription30Eur * this.selectedRange.length
undersubscription.CHF = this.paxCount * this.undersubscription30Chf * this.selectedRange.length
} else if (false === this.isSelfCatering && this.paxCount < 50) {
undersubscription.EUR = this.paxCount * this.undersubscription40Eur * this.selectedRange.length
undersubscription.CHF = this.paxCount * this.undersubscription40Chf * this.selectedRange.length
}
return {base, additional, undersubscription}
@@ -530,21 +687,9 @@ export default {
CHF: 0,
};
for (let option of this.selectedOptions) {
let optionPriceEUR = option.price.EUR
let optionPriceCHF = option.price.CHF
if (1 === option.type) {
price.EUR += optionPriceEUR;
price.CHF += optionPriceCHF;
} else if (2 === option.type) {
price.EUR += optionPriceEUR * this.selectedPax;
price.CHF += optionPriceCHF * this.selectedPax;
} else if (3 === option.type) {
price.EUR += optionPriceEUR * this.nightsCount;
price.CHF += optionPriceCHF * this.nightsCount;
} else if (4 === option.type) {
price.EUR += optionPriceEUR * this.nightsCount * this.selectedPax;
price.CHF += optionPriceCHF * this.nightsCount * this.selectedPax;
}
let optionPrice = this.calculateOptionPrice(option)
price.EUR += optionPrice.EUR;
price.CHF += optionPrice.CHF;
}
return price;
},
@@ -554,8 +699,8 @@ export default {
CHF: 0,
}
if (this.selectedBoard) {
price.EUR = this.selectedBoard.price.EUR * this.nightsCount * this.selectedPax;
price.CHF = this.selectedBoard.price.CHF * this.nightsCount * this.selectedPax;
price.EUR = this.selectedBoard.price.EUR * this.nightsCount * this.paxCount;
price.CHF = this.selectedBoard.price.CHF * this.nightsCount * this.paxCount;
}
return price
},
@@ -121,6 +121,11 @@ renderables:
type: Textarea
identifier: message
label: 'Deine Kontaktanfrage'
-
defaultValue: ''
type: Text
identifier: bookingNumber
label: 'Buchungsnummer (falls vorhanden)'
-
type: LinkedCheckbox
identifier: privacypolicyaccepted
@@ -87,12 +87,21 @@
<trans-unit id="tx_eptheme.message.group.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.address.1221560718">
<trans-unit id="tx_eptheme.message.street.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.postcode.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.city.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.phone.1221560718">
<source>Bitte angeben</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.board.1221560910">
<source>Bitte auswählen</source>
</trans-unit>
<trans-unit id="tx_eptheme.message.contactForm.firstName.1221560718">
<source>Bitte angeben</source>
</trans-unit>
@@ -264,6 +264,10 @@
font-weight:normal;
text-decoration:underline;
}
table.data th,
table.data td {
vertical-align: top;
}
@media only screen and (min-width:768px){
.templateContainer {
width:600px !important;
@@ -4,14 +4,20 @@
<f:cObject typoscriptObjectPath="lib.searchBarCollapsed"/>
<f:alias map="{searchbarCollapsed: 1}">
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
</f:alias>
<f:render section="Content"/>
<f:render section="Teasers"/>
<f:render partial="Footer" arguments="{_all}"/>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
<f:render partial="Search/MobileSearch" arguments="{_all}"/>
<main>
<f:render section="Content"/>
<f:render section="Teasers"/>
</main>
<footer>
<f:render partial="Footer" arguments="{_all}"/>
</footer>
<aside>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
<f:render partial="Search/MobileSearch" arguments="{_all}"/>
</aside>
<f:render partial="Config" arguments="{_all}" />
</html>
@@ -2,22 +2,30 @@
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
<f:render partial="HeaderSlider" arguments="{_all}"/>
<f:if condition="{data.tx_eptheme_hide_searchbar} || {settings.hideSearchbar}">
<f:else>
<f:cObject typoscriptObjectPath="lib.searchBar"/>
</f:else>
</f:if>
<f:render section="Content"/>
<f:render partial="Footer" arguments="{_all}"/>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
<f:if condition="{data.tx_eptheme_hide_searchbar} || {settings.hideSearchbar}">
<f:else>
<f:render partial="Search/MobileSearch" arguments="{_all}"/>
</f:else>
</f:if>
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
<header>
<f:render partial="HeaderSlider" arguments="{_all}"/>
</header>
<main>
<f:if condition="{data.tx_eptheme_hide_searchbar} || {settings.hideSearchbar}">
<f:else>
<f:cObject typoscriptObjectPath="lib.searchBar"/>
</f:else>
</f:if>
<f:render section="Content"/>
</main>
<footer>
<f:render partial="Footer" arguments="{_all}"/>
</footer>
<aside>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
<f:if condition="{data.tx_eptheme_hide_searchbar} || {settings.hideSearchbar}">
<f:else>
<f:render partial="Search/MobileSearch" arguments="{_all}"/>
</f:else>
</f:if>
</aside>
<f:render partial="Config" arguments="{_all}" />
</html>
@@ -3,12 +3,20 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
<f:render partial="EventHero" arguments="{_all}"/>
<f:render partial="EventDisturber" arguments="{headline: settings.eventDisturberHeadline, listItems: settings.eventDisturberListitems, buttonLabel: settings.eventDisturberButtonLabel, buttonLink: settings.eventDisturberButtonLink}"/>
<f:render section="Content"/>
<f:render partial="Footer" arguments="{_all}"/>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
<header>
<f:render partial="EventHero" arguments="{_all}"/>
<f:render partial="EventDisturber" arguments="{headline: settings.eventDisturberHeadline, listItems: settings.eventDisturberListitems, buttonLabel: settings.eventDisturberButtonLabel, buttonLink: settings.eventDisturberButtonLink}"/>
</header>
<main>
<f:render section="Content"/>
</main>
<footer>
<f:render partial="Footer" arguments="{_all}"/>
</footer>
<aside>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
</aside>
<f:render partial="Config" arguments="{_all}" />
</html>
@@ -3,16 +3,22 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
<f:if condition="{data.tx_eptheme_hide_searchbar} || {settings.hideSearchbar}">
<f:else>
<f:cObject typoscriptObjectPath="lib.searchBar"/>
</f:else>
</f:if>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
<f:render section="Content"/>
<f:render section="Teasers"/>
<f:render partial="Footer" arguments="{_all}"/>
<header>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
<f:if condition="{data.tx_eptheme_hide_searchbar} || {settings.hideSearchbar}">
<f:else>
<f:cObject typoscriptObjectPath="lib.searchBar"/>
</f:else>
</f:if>
</header>
<main>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
<f:render section="Content"/>
<f:render section="Teasers"/>
</main>
<footer>
<f:render partial="Footer" arguments="{_all}"/>
</footer>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
@@ -3,24 +3,30 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
<f:if condition="{data.tx_eptheme_hide_searchbar}">
<f:else>
<f:cObject typoscriptObjectPath="lib.searchBar"/>
</f:else>
</f:if>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
<f:render section="Header" optional="true"/>
<div class="container grid grid-cols-3 gap-8">
<div class="col-span-3 lg:col-span-1 order-last lg:order-none">
<f:render section="Sidebar"/>
<header>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
<f:if condition="{data.tx_eptheme_hide_searchbar}">
<f:else>
<f:cObject typoscriptObjectPath="lib.searchBar"/>
</f:else>
</f:if>
</header>
<main>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
<f:render section="Header" optional="true"/>
<div class="container grid grid-cols-3 gap-8">
<div class="col-span-3 lg:col-span-1 order-last lg:order-none">
<f:render section="Sidebar"/>
</div>
<div class="col-span-3 lg:col-span-2">
<f:render section="Content"/>
</div>
</div>
<div class="col-span-3 lg:col-span-2">
<f:render section="Content"/>
</div>
</div>
<f:render section="Teasers"/>
<f:render partial="Footer" arguments="{_all}"/>
<f:render section="Teasers"/>
</main>
<footer>
<f:render partial="Footer" arguments="{_all}"/>
</footer>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
@@ -3,19 +3,25 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
<f:render section="Header" optional="true"/>
<div class="container grid grid-cols-3 gap-8">
<div class="col-span-3 lg:col-span-1 order-last lg:order-none">
<f:render section="Sidebar"/>
<header>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
</header>
<main>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
<f:render section="Header" optional="true"/>
<div class="container grid grid-cols-3 gap-8">
<div class="col-span-3 lg:col-span-1 order-last lg:order-none">
<f:render section="Sidebar"/>
</div>
<div class="col-span-3 lg:col-span-2">
<f:render section="Content"/>
</div>
</div>
<div class="col-span-3 lg:col-span-2">
<f:render section="Content"/>
</div>
</div>
<f:render section="Teasers"/>
<f:render partial="Footer" arguments="{_all}"/>
<f:render section="Teasers"/>
</main>
<footer>
<f:render partial="Footer" arguments="{_all}"/>
</footer>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
@@ -3,24 +3,30 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
<f:if condition="{data.tx_eptheme_hide_searchbar}">
<f:else>
<f:cObject typoscriptObjectPath="lib.searchBar"/>
</f:else>
</f:if>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
<f:render section="Header" optional="true"/>
<div class="container grid grid-cols-3 gap-8">
<div class="col-span-3 lg:col-span-2">
<f:render section="Content"/>
<header>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
<f:if condition="{data.tx_eptheme_hide_searchbar}">
<f:else>
<f:cObject typoscriptObjectPath="lib.searchBar"/>
</f:else>
</f:if>
</header>
<main>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
<f:render section="Header" optional="true"/>
<div class="container grid grid-cols-3 gap-8">
<div class="col-span-3 lg:col-span-2">
<f:render section="Content"/>
</div>
<div class="col-span-3 lg:col-span-1">
<f:render section="Sidebar"/>
</div>
</div>
<div class="col-span-3 lg:col-span-1">
<f:render section="Sidebar"/>
</div>
</div>
<f:render section="Teasers"/>
<f:render partial="Footer" arguments="{_all}"/>
<f:render section="Teasers"/>
</main>
<footer>
<f:render partial="Footer" arguments="{_all}"/>
</footer>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
@@ -3,19 +3,25 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<f:render partial="Mainnav" section="Main" arguments="{_all}"/>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
<f:render section="Header" optional="true"/>
<div class="container grid grid-cols-3 gap-8">
<div class="col-span-3 lg:col-span-2">
<f:render section="Content"/>
<header>
<f:render partial="HeaderSliderSmall" arguments="{_all}"/>
<f:cObject typoscriptObjectPath="lib.breadcrumb"/>
</header>
<main>
<f:render section="Header" optional="true"/>
<div class="container grid grid-cols-3 gap-8">
<div class="col-span-3 lg:col-span-2">
<f:render section="Content"/>
</div>
<div class="col-span-3 lg:col-span-1">
<f:render section="Sidebar"/>
</div>
</div>
<div class="col-span-3 lg:col-span-1">
<f:render section="Sidebar"/>
</div>
</div>
<f:render section="Teasers"/>
<f:render partial="Footer" arguments="{_all}"/>
<f:render section="Teasers"/>
</main>
<footer>
<f:render partial="Footer" arguments="{_all}"/>
</footer>
<f:render partial="ContactBar" arguments="{_all}"/>
<f:render partial="Topnav" arguments="{_all}"/>
<f:render partial="Mobilenav" section="Main" arguments="{_all}"/>
@@ -3,13 +3,13 @@
xmlns:ep="http://typo3.org/ns/EP/EpTheme/ViewHelpers"
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<div class="container bg-white flex items-center justify-between">
<header class="container bg-white flex items-center justify-between">
<f:link.typolink parameter="{f:if(condition: settings.logoLink, then: settings.logoLink, else: settings.defaultHomeUid)}"
class="block lg:pb-4"
title="zur Startseite">
<f:image src="Logo"
<f:image src="{settings.logoImage}"
class="block w-auto {ep:themeClasses(classes: '{ep: \'h-10 lg:h-12\', sbw: \'h-14 lg:h-24\', snz: \'h-14 lg:h-24\', suz: \'h-14 lg:h-24\', uch: \'h-12 lg:h-20\', ser: \'h-10 lg:h-12\'}', key: settings.themekey)}"
alt="Logo"/>
alt="Bildmarke"/>
</f:link.typolink>
<f:link.typolink parameter="{settings.myEpPageUid}"
title="My E&P"
@@ -17,8 +17,12 @@
<div class="text-xl lg:text-2xl font-light leading-none">Login</div>
<div class="text-lg lg:text-xl font-medium leading-none">My E&P</div>
</f:link.typolink>
</div>
<f:render section="Content"/>
<f:render partial="Footer" arguments="{_all}"/>
</header>
<main>
<f:render section="Content"/>
</main>
<footer>
<f:render partial="Footer" arguments="{_all}"/>
</footer>
<f:render partial="Config" arguments="{_all}" />
</html>
@@ -5,7 +5,7 @@
<f:section name="Teaser">
<f:if condition="{concept}">
<a href="{f:uri.typolink(parameter: concept.detailPage)}" class="flex flex-col">
<div class="relative flex flex-col">
<div class="relative">
<f:alias map="{teaserImage: concept.teaserImage, defaultImage: concept.headerImage}">
<f:if condition="{teaserImage}">
@@ -33,17 +33,19 @@
</f:else>
</f:if>
</f:alias>
<div class="absolute top-0 left-0 inset-0 bg-concept-{concept.code}/70">
<div class="flex h-full w-full p-4 items-end headline--3 text-white">
<div class="absolute z-10 inset-0 bg-concept-{concept.code}/70">
<f:link.typolink parameter="{concept.detailPage}"
class="flex h-full w-full p-4 items-end headline--3 text-white">
{concept.name}
</div>
<span class="absolute inset-0"></span>
</f:link.typolink>
</div>
</div>
<div class="p-4 flex-1">
{concept.teaser -> f:format.crop(maxCharacters: settings.teaserTextMaxChars) -> f:format.html()}
</div>
<div class="bg-concept-{concept.code} h-1 w-20"></div>
</a>
</div>
</f:if>
</f:section>
@@ -8,7 +8,7 @@
<div>{data.bodytext -> f:format.html()}</div>
<div>
{data.flexForm.buttons -> v:iterator.first() -> v:variable.set(name: 'button')}
<a href="{f:uri.typolink(parameter: button.container.link)}" class="btn btn-info" title="{button.container.label -> f:format.raw()}">{button.container.label -> f:format.raw()}</a>
<a href="{f:uri.typolink(parameter: button.container.link)}" class="btn btn-info">{button.container.label -> f:format.raw()}</a>
</div>
</div>
@@ -16,7 +16,7 @@
<picture class="w-full h-full">
<source srcset="{f:uri.image(image: image, width: '1370')}" media="(min-width: 768px)">
<source srcset="{f:uri.image(image: image, height: '640', cropVariant: 'mobile')}" media="(max-width: 767px)">
<f:image image="{image}" cropVariant="mobile" height="640" class="w-full h-auto" alt="{image.alternative}"/>
<img src="{f:uri.image(image: image, cropVariant: 'mobile', height: '640')}" class="w-full h-auto" alt="{image.alternative}">
</picture>
</div>
</f:if>
@@ -34,6 +34,7 @@
</f:if>
<f:if condition="{slide.button}">
<f:link.typolink parameter="{slide.page_uid}"
additionalAttributes="{'tabindex': '-1'}"
class="button bg-button inline-block">
{slide.button}
</f:link.typolink>
@@ -44,7 +45,8 @@
<f:if condition="{slide.skipass} || {badge}">
<div class="herounit__badge-wrapper">
<f:if condition="{slide.skipass}">
<f:link.typolink parameter="{settings.skipassPageUid}">
<f:link.typolink parameter="{settings.skipassPageUid}"
additionalAttributes="{'tabindex': '-1'}">
<div class="herounit__badge herounit__badge--skipass">
<div class="uppercase text-4xl font-bold leading-7 mb-2">Ski-<br>Pass</div>
<div class="uppercase">inklusive</div>
@@ -53,14 +55,15 @@
</f:if>
<f:if condition="{badge}">
<f:alias map="{badgeData: badge.0.data}">
<f:link.typolink parameter="{badgeData.link}">
<f:link.typolink parameter="{badgeData.link}"
additionalAttributes="{'tabindex': '-1'}">
<div class="herounit__badge herounit__badge--custom">
<svg viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMinYMin meet"
class="w-full h-full">
<circle fill="currentColor" class="text-ep-primary-dark/50" cx="100" cy="100" r="100"/>
<circle fill="currentColor" class="text-ep-primary-dark" cx="100" cy="100" r="70"/>
<circle fill="currentColor" class="text-ep-primary/50" cx="100" cy="100" r="100"/>
<circle fill="currentColor" class="text-ep-primary" cx="100" cy="100" r="70"/>
</svg>
<div class="absolute top-0 left-0 inset-0 flex flex-col items-center justify-center p-8">
<span class="block text-center text-white uppercase font-bold leading-none text-xl">{badgeData.title -> f:format.raw()}</span>
@@ -7,7 +7,7 @@
<f:alias map="{image: slide.image.0}">
<f:if condition="{image}">
<picture>
<source srcset="{f:uri.image(image: image, width: '1370')}" media="(min-width: 768px)">
<source srcset="{f:uri.image(image: image, width: '1440')}" media="(min-width: 768px)">
<source srcset="{f:uri.image(image: image, height: '320', cropVariant: 'mobile')}" media="(max-width: 767px)">
<f:image class="block w-full h-full object-cover"
image="{image}"
@@ -24,7 +24,7 @@
data-lightbox-url-value="{f:uri.action(action: 'index', controller: 'GroupsPrice', arguments: '{hotel: hotel}', pageUid: settings.groupsPricePopupPageUid, absolute: 1)}"
data-lightbox-type-value="external"
data-action="lightbox#open">
Preisberechnung &amp; direkt buchen
Preisrechner/Buchungstool
</button>
</div>
</f:if>
@@ -4,23 +4,27 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers">
<div class="odd:bg-zinc-50 p-4" data-rte-content>
<h3>
Eigenanreise
</h3>
{journey.byCar -> f:format.html()}
<f:if condition="{journey.image}">
<ep:lazyLoadImage image="{journey.image}"
loaderimg="EXT:ep_theme/Resources/Public/images/lazy.png"
class="block w-full h-auto"
width="770"
height="430c+100"
alt="{journey.image.alternative}" />
</f:if>
<f:if condition="{journey.byBus}">
<h3>
Busanreise
</h3>
{journey.byBus -> f:format.html()}
<div class="pb-8">
{journey.byBus -> f:format.html()}
</div>
</f:if>
<f:if condition="{journey.byCar}">
<h3>
Eigenanreise
</h3>
{journey.byCar -> f:format.html()}
<f:if condition="{journey.image}">
<ep:lazyLoadImage image="{journey.image}"
loaderimg="EXT:ep_theme/Resources/Public/images/lazy.png"
class="block w-full h-auto"
width="770"
height="430c+100"
alt="{journey.image.alternative}" />
</f:if>
</f:if>
</div>
@@ -11,7 +11,7 @@
title="zur Startseite">
<f:image src="{settings.logoImage}"
class="block w-auto {ep:themeClasses(classes: '{ep: \'h-10 lg:h-12\', sbw: \'h-14 lg:h-24\', snz: \'h-14 lg:h-24\', suz: \'h-14 lg:h-24\', uch: \'h-12 lg:h-20\', ser: \'h-10 lg:h-12\'}', key: settings.themekey)}"
alt="Logo"/>
alt="Bildmarke"/>
</f:link.typolink>
<div class="hidden hover-hover:lg:block">
<f:render section="FirstLevel" arguments="{_all}"/>
@@ -43,10 +43,10 @@
</f:section>
<f:section name="FirstLevel">
<nav role="menu">
<ul class="m-0 p-0 list-none flex items-center justify-end space-x-4">
<nav role="navigation" aria-label="Hauptnavigation">
<ul class="m-0 p-0 list-none flex items-center justify-end space-x-4" role="menubar">
<f:for each="{mainnav}" as="topitem">
<li class="group pb-4">
<li class="group pb-4" role="none">
<f:if condition="{topitem.children}">
<f:then>
<a href="{f:uri.typolink(parameter: topitem.data.uid)}"
@@ -69,7 +69,7 @@
</f:for>
<f:if condition="{data.tx_eptheme_hide_searchbar} || {settings.hideSearchbar}">
<f:else>
<li class="pb-4">
<li class="pb-4" role="none">
<f:if condition="{searchbarCollapsed}">
<f:then>
<button type="button"
@@ -80,7 +80,7 @@
</button>
</f:then>
<f:else>
<f:link.typolink parameter="{settings.defaultSearchPageUid}" title="zur Suche">
<f:link.typolink parameter="{settings.defaultSearchPageUid}" title="zur Suche" additionalAttributes="{'role': 'menuitem'}">
{ep:icon(icon: 'search', class: 'w-6 h-6')}
</f:link.typolink>
</f:else>
@@ -96,11 +96,11 @@
<div class="absolute top-100 left-0 inset-x-0 pt-4 invisible opacity-0 group-hover:visible group-hover:opacity-100 transition-opacity duration-300 ease-in-out">
<div class="bg-white grid grid-cols-3 lg:grid-cols-4 gap-8 p-4 shadow-lg rounded">
<f:for each="{topitem.children}" as="subitem">
<ul class="m-0 p-0 list-none">
<li>
<ul class="m-0 p-0 list-none" role="menu">
<li role="none">
<f:if condition="{subitem.doktype} == 3">
<f:then>
<a href="{subitem.link}" target="_blank">
<a href="{subitem.link}" target="_blank" role="menuitem">
<f:if condition="{subitem.data.tx_eptheme_context_country_code}">
{ep:icon(icon: 'flag-{subitem.data.tx_eptheme_context_country_code -> f:format.case(mode: \'lower\')}', class: 'w-8 h-6')}
</f:if>
@@ -111,6 +111,7 @@
</f:then>
<f:else>
<a href="{f:uri.typolink(parameter: subitem.data.uid)}"
role="menuitem"
class="flex items-center space-x-2 underlined">
<f:if condition="{subitem.data.tx_eptheme_context_country_code}">
{ep:icon(icon: 'flag-{subitem.data.tx_eptheme_context_country_code -> f:format.case(mode: \'lower\')}', class: 'w-8 h-6')}
@@ -133,18 +134,20 @@
<f:for each="{subitem.children}" as="subsubitem">
<f:if condition="{subsubitem.doktype} == 3">
<f:then>
<li>
<li role="none">
<a href="{subsubitem.link}"
class="text-zinc-800 text-lg"
target="_blank">
target="_blank"
role="menuitem">
{subsubitem.title}
</a>
</li>
</f:then>
<f:else>
<li>
<li role="none">
<a href="{f:uri.typolink(parameter: subsubitem.data.uid)}"
class="text-zinc-800 text-lg">
class="text-zinc-800 text-lg"
role="menuitem">
{subsubitem.title}
</a>
</li>
@@ -14,7 +14,7 @@
<f:link.typolink parameter="{f:if(condition: settings.logoLink, then: settings.logoLink, else: settings.defaultHomeUid)}"
class="block hover-hover:lg:pb-4"
title="zur Startseite">
<f:image class="block h-10 lg:h-12 w-auto" src="{settings.logoImage}" alt="Logo"/>
<f:image class="block h-10 lg:h-12 w-auto" src="{settings.logoImage}" alt="Bildmarke"/>
</f:link.typolink>
<div class="flex hover-hover:lg:hidden items-center space-x-4">
<f:if condition="{data.tx_eptheme_hide_searchbar}">
@@ -12,8 +12,7 @@
<f:alias map="{mediaElement: '{newsItem.mediaPreviews.0}'}">
<img class="block w-full h-auto lazyload"
data-src="{f:uri.image(image: mediaElement, width: '640', height: '360c+50')}"
alt="{mediaElement.originalResource.alternative}"
title="{mediaElement.originalResource.title}">
alt="{mediaElement.originalResource.alternative}">
</f:alias>
</f:if>
</f:then>
@@ -22,8 +21,7 @@
<f:alias map="{mediaElement: '{newsItem.media.0}'}">
<img class="block w-full h-auto lazyload"
data-src="{f:uri.image(image: mediaElement, width: '640', height: '360c+50')}"
alt="{mediaElement.originalResource.alternative}"
title="{mediaElement.originalResource.title}">
alt="{mediaElement.originalResource.alternative}">
</f:alias>
</f:if>
</f:else>
@@ -40,7 +38,7 @@
<time datetime="{f:format.date(date:newsItem.datetime, format:'Y-m-d')}"><f:format.date format="{f:translate(key:'dateFormat')}">{newsItem.datetime}</f:format.date></time>
<f:if condition="{newsItem.tags}"> in
<f:for each="{newsItem.tags}" as="tag" iteration="iteration">
<f:link.page title="{tag.title}" pageUid="{settings.tagListPageUid}" additionalParams="{tx_news_pi1:{overwriteDemand:{tags: tag}}}">
<f:link.page pageUid="{settings.tagListPageUid}" additionalParams="{tx_news_pi1:{overwriteDemand:{tags: tag}}}">
{tag.title}
</f:link.page>
{f:if(condition: iteration.isLast, else: ', ')}
@@ -59,7 +57,7 @@
</f:if>
</div>
</n:removeMediaTags>
<n:link newsItem="{newsItem}" settings="{settings}" class="button bg-button button--small" title="{newsItem.title}">
<n:link newsItem="{newsItem}" settings="{settings}" class="button bg-button button--small">
<f:translate key="more-link"/>
</n:link>
</div>
@@ -3,7 +3,7 @@
xmlns:f="http://typo3.org/ns/fluid/ViewHelpers"
xmlns:ep="http://typo3.org/ns/EP/EpTheme/ViewHelpers">
<f:if condition="{pricetable -> f:count()} == 0">
<f:if condition="{pricetable.available -> f:count()} == 0">
<f:then>
<div class="alert alert-info" data-rte-content>
{nonBookableInfo.text -> f:format.html()}
@@ -32,7 +32,7 @@
</tr>
</thead>
<tbody>
<f:for each="{pricetable}" as="row" iteration="iteration">
<f:for each="{pricetable.available}" as="row" iteration="iteration">
<tr class="even:bg-zinc-50">
<td class="px-2 py-1 border border-zinc-200 whitespace-nowrap">
{row.roomName}
@@ -88,8 +88,7 @@
data-pricetable-type-param="options"
data-pricetable-uid-param="{row.roomUid}"
data-cooltipz-dir="top"
aria-label="Alle Zusatzleistungen anzeigen"
role="tooltip">
aria-label="Alle Zusatzleistungen anzeigen">
{ep:icon(icon: 'plus-circle', class: 'w-4 h-4 md:w-5 md:h-5')}
</button>
</f:if>
@@ -137,7 +136,7 @@
</button>
</div>
<div class="h-128 p-4 overflow-y-scroll bg-white rounded-b">
<f:for each="{pricetable}" as="row">
<f:for each="{pricetable.available}" as="row">
<div class="hidden"
data-modal-target="content"
data-uid="{row.roomUid}">
@@ -12,7 +12,7 @@
data-lightbox-url-value="{f:uri.action(action: 'index', controller: 'GroupsPrice', arguments: '{hotel: hotel}', pageUid: settings.groupsPricePopupPageUid, absolute: 1)}"
data-lightbox-type-value="external"
data-action="lightbox#open">
Preisberechnung &amp; direkt buchen
Preisrechner/Buchungstool
</button>
</div>
</f:if>
@@ -14,7 +14,7 @@
</f:if>
<div class="herounit h-80 lg:h-96 mb-8">
<picture>
<source srcset="{f:uri.image(image: image, width: '1370c', height: '380c')}" media="(min-width: 768px)">
<source srcset="{f:uri.image(image: image, width: '1440c', height: '380c')}" media="(min-width: 768px)">
<source srcset="{f:uri.image(image: image, height: '320c')}" media="(max-width: 767px)">
<f:image class="block w-full h-full object-cover"
image="{image}"
@@ -68,7 +68,6 @@
<f:if condition="{settings.showSkipassBadge} && {product.skipassIncluded}">
<div class="herounit__badge-wrapper">
<f:link.typolink parameter="{settings.skipassPageUid}"
title="Skipass inklusive"
class="herounit__badge herounit__badge--skipass">
<p class="uppercase text-4xl font-bold leading-7 mb-2">
Ski-<br>Pass
@@ -57,8 +57,7 @@
data-watchlist-toggle-target="ariaLabel"
data-action="watchlist-toggle#toggle"
data-cooltipz-dir="top-right"
aria-label="auf die Merkliste setzen"
role="tooltip">
aria-label="auf die Merkliste setzen">
<svg class="product-teaser__watchlist-icon" data-watchlist-toggle-target="icon" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"></path></svg>
</button>
</div>
@@ -17,8 +17,7 @@
loaderimg="EXT:ep_theme/Resources/Public/images/lazy.png"
class="block w-full h-auto"
width="330"
alt="Webcam {region.name}"
title="Webcam {region.name}" />
alt="Webcam {region.name}" />
</a>
</div>
</f:if>
@@ -11,7 +11,7 @@
<a href="{f:uri.typolink(parameter: settings.defaultHomeUid)}"
class="block hover-hover:lg:pb-4"
title="zur Startseite">
<f:image class="block h-10 lg:h-12 w-auto" src="{settings.logoImage}" alt="Logo"/>
<f:image class="block h-10 lg:h-12 w-auto" src="{settings.logoImage}" alt="Bildmarke"/>
</a>
<button type="button"
aria-label="Suche schließen"
@@ -11,7 +11,6 @@
<div class="tiles__tile lg:col-span-2 lg:row-span-1"
style="background-image:url('{f:uri.image(image: images.0)}');">
<f:link.typolink parameter="{images.0.link}"
title="{images.0.title -> f:format.raw()}"
class="tiles__link">
<span class="tiles__label">
{images.0.title -> f:format.raw()}
@@ -23,7 +22,6 @@
<div class="tiles__tile lg:col-span-2 lg:row-span-1"
style="background-image:url('{f:uri.image(image: images.1)}');">
<f:link.typolink parameter="{images.1.link}"
title="{images.1.title -> f:format.raw()}"
class="tiles__link">
<span class="tiles__label">
{images.1.title -> f:format.raw()}
@@ -35,7 +33,6 @@
<div class="tiles__tile lg:col-span-12 lg:row-span-1"
style="background-image:url('{f:uri.image(image: images.4)}');">
<f:link.typolink parameter="{images.4.link}"
title="{images.4.title -> f:format.raw()}"
class="tiles__link">
<span class="tiles__label">
{images.4.title -> f:format.raw()}
@@ -47,7 +44,6 @@
<div class="tiles__tile lg:col-span-4 lg:row-span-2"
style="background-image:url('{f:uri.image(image: images.2)}');">
<f:link.typolink parameter="{images.2.link}"
title="{images.2.title -> f:format.raw()}"
class="tiles__link">
<span class="tiles__label">
{images.2.title -> f:format.raw()}
@@ -59,7 +55,6 @@
<div class="tiles__tile lg:col-span-6 lg:row-span-2"
style="background-image:url('{f:uri.image(image: images.3)}');">
<f:link.typolink parameter="{images.3.link}"
title="{images.3.title -> f:format.raw()}"
class="tiles__link">
<span class="tiles__label">
{images.3.title -> f:format.raw()}
@@ -7,7 +7,7 @@
<f:else>
<div class="topnav">
<div class="container">
<nav role="navigation" class="topnav__nav">
<nav role="navigation" aria-label="Bereichs-Navigation" class="topnav__nav">
<div class="flex items-center border-l border-white/50">
<f:if condition="{settings.mainSitePid}">
<f:link.typolink parameter="{settings.mainSitePid}"
@@ -15,7 +15,7 @@
title="zur Startseite">
<f:image src="EXT:ep_theme/Resources/Public/images/logo.svg"
class="h-5 w-auto"
alt="Logo"/>
alt=""/>
</f:link.typolink>
</f:if>
<f:link.typolink parameter="{settings.defaultHomeUid}" class="topnav__item" title="E&amp;P">
@@ -7,7 +7,7 @@
<f:section name="Main">
<div class="container relative">
<div class="h-80 lg:h-128">
<f:image image="{travelinfo.headerImage}"
<f:image image="{travelInfo.headerImage}"
width="1280"
alt="Reiseinformationen"
class="block w-full h-full object-cover object-center" />
@@ -18,8 +18,8 @@
</div>
<div class="grid grid-cols-2 lg:grid-cols-4">
<f:variable name="bgColors" value="{0: 'bg-[#165883]/90', 1: 'bg-[#9DBACE]/90', 2: 'bg-[#3396E4]/90', 3: 'bg-[#0268AA]/90'}"/>
<f:variable name="linksCount" value="{travelinfo.links -> f:count()}"/>
<f:for each="{travelinfo.links}" as="link" iteration="iteration">
<f:variable name="linksCount" value="{travelInfo.links -> f:count()}"/>
<f:for each="{travelInfo.links}" as="link" iteration="iteration">
<f:render section="Link" arguments="{item: link, color: '{bgColors.{iteration.index}}'}"/>
</f:for>
<a href="#goodtoknow"
@@ -40,13 +40,13 @@
<div class="pb-32 pt-32 lg:pt-52 -mt-24 bg-ep-primary-bg">
<div class="container px-8 lg:px-24" data-rte-content>
<h1 class="headline--underlined headline--has-subline">
<span class="subline">{travelinfo.subline}</span>
{travelinfo.headline}
<span class="subline">{travelInfo.subline}</span>
{travelInfo.headline}
</h1>
{travelinfo.introText -> f:format.html()}
{travelInfo.introText -> f:format.html()}
<f:if condition="{date}">
<p>
{date.product.name} {date.dateStart -> f:format.date(format: 'd.m.Y')} - {date.dateEnd -> f:format.date(format: 'd.m.Y')}
{travelData.label} {travelData.dateFrom -> f:format.date(format: 'd.m.Y')} - {travelData.dateTo -> f:format.date(format: 'd.m.Y')}
</p>
</f:if>
</div>
@@ -63,20 +63,22 @@
</div>
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
<f:if condition="{date}">
<f:if condition="{travelinfo.hideAddress}">
<f:if condition="{travelInfo.hideAddress}">
<f:else>
<h3 class="text-white">
Adresse
</h3>
<p>
{date.hotel.name}
{travelData.hotel.name}
<br>
{date.hotel.address -> f:format.nl2br()}
{travelData.hotel.street}
<br>
{travelData.hotel.country} {travelData.hotel.city}
</p>
</f:else>
</f:if>
<f:if condition="{date.pickups -> f:count()} > 0">
<f:if condition="{date.pickups -> f:count()} <= 3">
<f:if condition="{travelData.pickupsTo -> f:count()} > 0">
<f:if condition="{travelData.pickupsTo -> f:count()} <= 3">
<f:then>
<f:render section="Pickups" arguments="{_all}"/>
</f:then>
@@ -98,11 +100,11 @@
</f:if>
</f:if>
</f:if>
<f:if condition="{travelinfo.selfArrangedText}">
<f:if condition="{travelInfo.selfArrangedText}">
<h3 class="text-white">
Eigenanreise
</h3>
{travelinfo.selfArrangedText -> f:format.html()}
{travelInfo.selfArrangedText -> f:format.html()}
</f:if>
</div>
</div>
@@ -114,13 +116,13 @@
</h2>
</div>
<div class="container px-4 lg:px-24 flex flex-col space-y-16 pb-16">
<f:for each="{travelinfo.teasers}" as="teaser">
<f:for each="{travelInfo.teasers}" as="teaser">
<f:render section="Teaser" arguments="{_all}"/>
</f:for>
</div>
<div id="goodtoknow" class="bg-ep-primary-bg">
<div class="container">
<f:if condition="{travelinfo.additionalInfo}">
<f:if condition="{travelInfo.additionalInfo}">
<div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white bg-[#B48F1D] px-8 lg:px-0 py-8">
<div class="lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8">
<div class="w-32 h-32 xl:w-16 xl:h-16 text-[#F9C700]">
@@ -131,11 +133,11 @@
</div>
</div>
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
{travelinfo.additionalInfo -> f:format.html()}
{travelInfo.additionalInfo -> f:format.html()}
</div>
</div>
</f:if>
<f:if condition="{travelinfo.importantPhoneNumbers}">
<f:if condition="{travelInfo.importantPhoneNumbers}">
<div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white bg-[#F8C62F] px-8 lg:px-0 py-8">
<div class="lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8">
<div class="text-2xl lg:text-4xl text-white uppercase font-bold">
@@ -143,18 +145,18 @@
</div>
</div>
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
<f:if condition="{date.guide}">
<f:if condition="{travelData.guide}">
<p>
<strong>Busbegleitung / Reiseleitung</strong>
<br>
{date.guide.name} {date.guide.phone}
{travelData.guide.name} {travelData.guide.phone}
</p>
</f:if>
{travelinfo.importantPhoneNumbers -> f:format.html()}
{travelInfo.importantPhoneNumbers -> f:format.html()}
</div>
</div>
</f:if>
<f:if condition="{travelinfo.importantLinks}">
<f:if condition="{travelInfo.importantLinks}">
<div class="flex flex-col lg:flex-row divide-y lg:divide-y-0 lg:divide-x divide-white bg-[#18527B] px-8 lg:px-0 py-8">
<div class="lg:w-1/4 flex flex-col space-y-4 pb-8 lg:pb-0 lg:px-8">
<div class="text-2xl lg:text-4xl text-white uppercase font-bold">
@@ -162,18 +164,18 @@
</div>
</div>
<div class="lg:w-3/4 pt-8 lg:pt-0 lg:px-8 text-white" data-rte-content>
{travelinfo.importantLinks -> f:format.html()}
{travelInfo.importantLinks -> f:format.html()}
</div>
</div>
</f:if>
</div>
</div>
<div class="container px-4 lg:px-24 py-16">
{travelinfo.footerText -> f:format.html()}
{travelInfo.footerText -> f:format.html()}
<f:image src="EXT:ep_theme/Resources/Public/images/signature_ep_team.png" class="block w-full max-w-64 h-auto mt-8" alt="" />
</div>
<div class="container">
<f:image image="{travelinfo.footerImage}" width="1028" class="block w-full h-auto" alt="" />
<f:image image="{travelInfo.footerImage}" width="1028" class="block w-full h-auto" alt="" />
</div>
</f:section>
@@ -196,20 +198,28 @@
<h2 class="headline--primary">
{teaser.title}
</h2>
<f:if condition="{date}">
<f:if condition="{travelData}">
<f:if condition="{teaser.category} == 'food'">
<p>
{date.board}
</p>
<f:if condition="{board -> f:count()} > 0">
<f:then>
<ul>
<f:for as="item" each="{board}">
<li>
{item.label}
</li>
</f:for>
</ul>
</f:then>
</f:if>
</f:if>
<f:if condition="{teaser.category} == 'course'">
<f:if condition="{date.activeCoursesForTravelinfo -> f:count()} > 0">
<f:if condition="{courses -> f:count()} > 0">
<f:then>
<p>
<strong>Folgende Kurse finden statt</strong>
</p>
<ul>
<f:for as="course" each="{date.activeCoursesForTravelinfo}">
<f:for as="course" each="{courses}">
<li>
{course.label}
</li>
@@ -257,7 +267,7 @@
<p>
Bitte findet euch mindestens <strong>20 Minuten vor Abfahrt</strong> an der gebuchten Haltestelle ein.
</p>
{travelinfo.busInfoText -> f:format.html()}
{travelInfo.busInfoText -> f:format.html()}
<table class="w-full bg-zinc-200 text-zinc-800 text-xs md:text-base mb-4">
<tr>
<th class="border-r border-white/50 px-2 py-1 md:p-2 text-left text-sm md:text-base">
@@ -267,18 +277,23 @@
Datum/Uhrzeit
</th>
</tr>
<f:for each="{date.pickups}" as="pickup">
<f:for each="{travelData.pickupsTo}" as="pickup">
<tr class="odd:bg-zinc-50 align-top">
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
{pickup.city}<br><span class="text-xs">{pickup.street}</span>
</td>
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
{pickup.date}<br>{f:if(condition: pickup.time, then: '{pickup.time} Uhr', else: '???')}
{pickup.time -> f:format.date(format: 'd.m.Y')}<br>{pickup.time -> f:format.date(format: 'H:i')} Uhr
</td>
</tr>
</f:for>
</table>
{travelinfo.departureText -> f:format.html()}
<f:if condition="{departureDayTime}">
<p>
Die Rückreise mit dem Bus startet <strong>{departureDayTime}</strong>.
</p>
</f:if>
{travelInfo.departureText -> f:format.html()}
</f:section>
<f:section name="Icon">
@@ -58,8 +58,7 @@
data-daytrip-type-param="services"
data-daytrip-uid-param="{iteration.cycle}"
data-cooltipz-dir="top"
aria-label="Alle Inklusivleistungen anzeigen"
role="tooltip">
aria-label="Alle Inklusivleistungen anzeigen">
<svg class="w-4 h-4">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-info-circle"></use>
</svg>
@@ -20,12 +20,12 @@
<f:section name="Table">
<div class="w-full max-w-full overflow-x-scroll md:overflow-x-auto">
<table class="min-w-full border border-zinc-200 border-collapse mb-8">
<table class="min-w-full border border-zinc-200 border-collapse mb-2">
<f:for each="{categories}" as="category" key="categoryKey">
<f:if condition="{v:variable.get(name: 'pricetable.{categoryKey}', useRawKeys: 1) -> f:count()} > 0">
<tr class="text-white {ep:themeClasses(classes: '{sbw: \'bg-sbw-primary\', snz: \'bg-snz-primary\', uch: \'bg-uch-primary\'}', key: themekey)}">
<td class="px-2 py-1 font-bold border border-zinc-200">
Apartments {category}
{category}
</td>
<f:for each="{roomTypesLabels}" as="type">
<td class="px-2 py-1 border border-zinc-200">
@@ -36,33 +36,34 @@
<f:for each="{pricetable.{categoryKey}}" as="row">
<tr>
<td class="px-2 py-1 border border-zinc-200">
<f:if condition="{row.hotelShortName}">
<f:then>
{row.hotelShortName}
</f:then>
<f:else>
<f:if condition="{row.hotelTitle}">
<f:then>
{row.hotelTitle}
</f:then>
<f:else>
{row.hotelName}
</f:else>
</f:if>
</f:else>
</f:if>
<f:link.typolink parameter="{row.hotelUri}" class="underline">
<f:if condition="{row.hotelShortName}">
<f:then>
{row.hotelShortName}
</f:then>
<f:else>
<f:if condition="{row.hotelTitle}">
<f:then>
{row.hotelTitle}
</f:then>
<f:else>
{row.hotelName}
</f:else>
</f:if>
</f:else>
</f:if>
</f:link.typolink>
</td>
<f:for each="{roomTypesPax}" as="type">
<td class="px-2 py-1 border border-zinc-200">
<f:if condition="{row.rooms.{type}.available}">
<f:then>
<a class="{ep:themeClasses(classes: '{sbw: \'text-sbw-primary\', snz: \'text-snz-primary\', uch: \'text-uch-primary\'}', key: settings.themekey)}"
data-controller="datalayer" data-datalayer-event-value="ZurBuchung" data-action="datalayer#trigger"
href="{row.bookingUrl}"
title="Jetzt buchen"
target="_blank">
<f:link.typolink
parameter="{row.hotelUri}"
class="whitespace-nowrap {ep:themeClasses(classes: '{sbw: \'text-sbw-primary\', snz: \'text-snz-primary\', uch: \'text-uch-primary\'}', key: settings.themekey)}">
{row.rooms.{type}.price}
</a>
{f:if(condition: '{row.rooms.{type}.undersubscription}', then: '*')}
</f:link.typolink>
</f:then>
<f:else>
<f:if condition="{row.rooms.{type}.available}">
@@ -79,6 +80,7 @@
</f:if>
</f:for>
</table>
<p class="text-sm mb-8">* Preis kommt durch eine Unterbelegung zustande.</p>
</div>
</f:section>
@@ -47,129 +47,17 @@
</tr>
</thead>
<tbody>
<f:for each="{prices}" as="row">
<tr class="odd:bg-zinc-50 hover:bg-zinc-100 odd:hover:bg-zinc-100"
data-pricetable-target="row">
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
<f:if condition="{row.showBookingButton} && {row.available}">
<f:then>
<a class="inline-block text-ep-primary-light text-xs sm:text-sm md:text-base leading-tight"
href="{row.bookingUrl}"
data-controller="datalayer" data-datalayer-event-value="ZurBuchung" data-datalayer-action="trigger"
target="_blank"
title="{row.roomName}, {hotel.name}, {dateRange} buchen">
{row.roomName}
</a>
</f:then>
<f:else>
<span class="inline-block text-xs sm:text-sm md:text-base line-through leading-tight">
{row.roomName}
</span>
<f:if condition="{row.available}">
<f:else>
<span class="inline-block text-xs sm:text-sm md:text-base text-sm leading-tight lg:ml-2">ausgebucht</span>
</f:else>
</f:if>
</f:else>
</f:if>
</td>
<f:if condition="{hasIncludedServices}">
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
<div class="flex items-center justify-around space-x-1">
<div class="grid grid-cols-2">
<f:if condition="{row.busIncluded}">
<f:then>
<span class="block p-1"
data-cooltipz-dir="top"
aria-label="Busfahrt inklusive"
role="tooltip"
data-pricetable-target="busIcon"
data-bus-included="{row.busIncluded}"
data-bus-price="{row.busPrice}">
<svg class="w-4 h-4 md:w-5 md:h-5">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-bus"></use>
</svg>
</span>
</f:then>
<f:else>
<span class="block"></span>
</f:else>
</f:if>
<f:if condition="{row.skipassIncluded}">
<f:then>
<span class="block p-1"
data-cooltipz-dir="top"
aria-label="{f:if(condition: row.summer, then: 'Bergbahnticket inklusive', else: 'Skipass inklusive')}"
role="tooltip">
<svg class="w-4 h-4 md:w-5 md:h-5">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-tag"></use>
</svg>
</span>
</f:then>
<f:else>
<span class="block"></span>
</f:else>
</f:if>
</div>
</div>
</td>
</f:if>
<td class="border-r border-zinc-200 px-2 py-1 md:p-2 text-center">
<f:if condition="{row.hasOptionalServices} && {row.available}">
<button type="button"
class="text-ep-primary-light text-sm md:text-base text-left leading-none p-1"
data-action="pricetable#openModal"
data-pricetable-type-param="options"
data-pricetable-uid-param="{row.roomUid}"
data-cooltipz-dir="top"
aria-label="Alle Zusatzleistungen anzeigen"
role="tooltip">
Zusatz&shy;leistungen
</button>
</f:if>
</td>
<td class="px-2 py-1 md:p-2 whitespace-nowrap">
<f:if condition="{row.showBookingButton}">
<f:then>
<f:if condition="{row.available}">
<f:then>
<a class="button bg-button button--small w-full"
data-controller="datalayer" data-datalayer-event-value="ZurBuchung" data-action="datalayer#trigger"
href="{row.bookingUrl}"
title="{row.roomName}, {hotel.name}, {dateRange} buchen"
target="_blank">
<span class="font-bold normal-case whitespace-nowrap"
data-pricetable-target="price"
data-room="{row.minPrice}"
data-bus="{row.busPrice}"
data-discount="{row.discount}">
{row.minPrice}
</span>
</a>
</f:then>
<f:else>
<span class="button bg-button bg-button--muted button--small block cursor-not-allowed font-bold normal-case whitespace-nowrap"
data-cooltipz-dir="top"
aria-label="Leider ausgebucht"
role="tooltip"
data-pricetable-target="price"
data-room="{row.minPrice}"
data-bus="{row.busPrice}"
data-discount="{row.discount}">
{row.minPrice} €
</span>
</f:else>
</f:if>
</f:then>
<f:else>
<f:link.typolink parameter="{settings.travelAlertPageUid}"
class="button bg-button button--small">
Reisen-Alert
</f:link.typolink>
</f:else>
</f:if>
</td>
</tr>
<f:comment><!--
Render available and unavailable rooms separately to maintain order:
1. by pax
2. by price
3. by availability
--></f:comment>
<f:for each="{prices.available}" as="row">
<f:render section="Row" arguments="{_all}"/>
</f:for>
<f:for each="{prices.unavailable}" as="row">
<f:render section="Row" arguments="{_all}"/>
</f:for>
</tbody>
</table>
@@ -277,34 +165,161 @@
</button>
</div>
<div class="h-128 p-4 overflow-y-scroll bg-white rounded-b">
<f:for each="{prices}" as="row">
<div class="hidden"
data-modal-target="content"
data-uid="{row.roomUid}">
<f:for each="{row.optionalServices}" as="section" key="sectionCode">
<div class="py-2 -mx-1">
<div class="font-bold text-xl px-1">
{section.label}
</div>
<f:for each="{section.services}" as="service">
<f:if condition="{sectionCode} == 'BUS' || {service.price} != '0,00'">
<dl class="flex items-start justify-between p-1 hover:bg-zinc-50">
<dt class="font-normal">
{service.label}:
</dt>
<dd class="whitespace-nowrap">
{service.price} €
</dd>
</dl>
</f:if>
</f:for>
</div>
</f:for>
</div>
<f:for each="{prices.available}" as="row">
<f:render section="OptionsModalRow" arguments="{_all}"/>
</f:for>
</div>
</div>
</div>
</f:section>
<f:section name="Row">
<tr class="odd:bg-zinc-50 hover:bg-zinc-100 odd:hover:bg-zinc-100"
data-pricetable-target="row">
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
<f:if condition="{row.showBookingButton} && {row.available}">
<f:then>
<a class="inline-block text-ep-primary-light text-xs sm:text-sm md:text-base leading-tight"
href="{row.bookingUrl}"
data-controller="datalayer" data-datalayer-event-value="ZurBuchung" data-datalayer-action="trigger"
target="_blank"
title="{row.roomName}, {hotel.name}, {dateRange} buchen">
{row.roomName}
</a>
</f:then>
<f:else>
<span class="inline-block text-xs sm:text-sm md:text-base line-through leading-tight">{row.roomName}</span>
<f:if condition="{row.available}">
<f:else>
<span class="inline-block text-xs sm:text-sm md:text-base text-sm leading-tight lg:ml-2">ausgebucht</span>
</f:else>
</f:if>
</f:else>
</f:if>
</td>
<f:if condition="{hasIncludedServices}">
<td class="border-r border-zinc-200 px-2 py-1 md:p-2">
<div class="flex items-center justify-around space-x-1">
<div class="grid grid-cols-2">
<f:if condition="{row.busIncluded}">
<f:then>
<span class="block p-1"
data-cooltipz-dir="top"
aria-label="Busfahrt inklusive"
role="tooltip"
data-pricetable-target="busIcon"
data-bus-included="{row.busIncluded}"
data-bus-price="{row.busPrice}">
<svg class="w-4 h-4 md:w-5 md:h-5">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-bus"></use>
</svg>
</span>
</f:then>
<f:else>
<span class="block"></span>
</f:else>
</f:if>
<f:if condition="{row.skipassIncluded}">
<f:then>
<span class="block p-1"
data-cooltipz-dir="top"
aria-label="{f:if(condition: row.summer, then: 'Bergbahnticket inklusive', else: 'Skipass inklusive')}"
role="tooltip">
<svg class="w-4 h-4 md:w-5 md:h-5">
<use href="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/icons_sprite.svg')}#icon-tag"></use>
</svg>
</span>
</f:then>
<f:else>
<span class="block"></span>
</f:else>
</f:if>
</div>
</div>
</td>
</f:if>
<td class="border-r border-zinc-200 px-2 py-1 md:p-2 text-center">
<f:if condition="{row.hasOptionalServices} && {row.available}">
<button type="button"
class="text-ep-primary-light text-sm md:text-base text-left leading-none p-1"
data-action="pricetable#openModal"
data-pricetable-type-param="options"
data-pricetable-uid-param="{row.roomUid}"
data-cooltipz-dir="top"
aria-label="Alle Zusatzleistungen anzeigen">
Zusatz&shy;leistungen
</button>
</f:if>
</td>
<td class="px-2 py-1 md:p-2 whitespace-nowrap">
<f:if condition="{row.showBookingButton}">
<f:then>
<f:if condition="{row.available}">
<f:then>
<a class="button bg-button button--small w-full"
data-controller="datalayer" data-datalayer-event-value="ZurBuchung" data-action="datalayer#trigger"
href="{row.bookingUrl}"
title="{row.roomName}, {hotel.name}, {dateRange} buchen"
target="_blank">
<span class="font-bold normal-case whitespace-nowrap"
data-pricetable-target="price"
data-room="{row.minPrice}"
data-bus="{row.busPrice}"
data-discount="{row.discount}">
{row.minPrice}
</span>
</a>
</f:then>
<f:else>
<span class="button bg-button bg-button--muted button--small block cursor-not-allowed font-bold normal-case whitespace-nowrap"
data-cooltipz-dir="top"
aria-label="Leider ausgebucht"
role="tooltip"
data-pricetable-target="price"
data-room="{row.minPrice}"
data-bus="{row.busPrice}"
data-discount="{row.discount}">
{row.minPrice} €
</span>
</f:else>
</f:if>
</f:then>
<f:else>
<f:link.typolink parameter="{settings.travelAlertPageUid}"
class="button bg-button button--small">
Reisen-Alert
</f:link.typolink>
</f:else>
</f:if>
</td>
</tr>
</f:section>
<f:Section name="OptionsModalRow">
<div class="hidden"
data-modal-target="content"
data-uid="{row.roomUid}">
<f:for each="{row.optionalServices}" as="section" key="sectionCode">
<f:if condition="{section.services}">
<div class="py-2 -mx-1">
<div class="font-bold text-xl px-1">
{section.label}
</div>
<f:for each="{section.services}" as="service">
<f:if condition="{sectionCode} == 'BUS' || {sectionCode} == 'VPF' || {service.price} != '0,00'">
<dl class="flex items-start justify-between p-1 hover:bg-zinc-50">
<dt class="font-normal">
{service.label}:
</dt>
<dd class="whitespace-nowrap">
{service.price} €
</dd>
</dl>
</f:if>
</f:for>
</div>
</f:if>
</f:for>
</div>
</f:Section>
</html>
@@ -13,7 +13,7 @@
<h1>Anfrage Gruppenhaus vom <f:format.date date="now" format="d.m.y" /></h1>
</f:else>
</f:if>
<table>
<table class="data">
<tr>
<th>
Art
@@ -35,8 +35,16 @@
<td>{group}</td>
</tr>
<tr>
<th>Adresse</th>
<td>{address -> f:format.nl2br()}</td>
<th>Straße</th>
<td>{street}</td>
</tr>
<tr>
<th>Postleitzahl</th>
<td>{postcode}</td>
</tr>
<tr>
<th>Ort</th>
<td>{city}</td>
</tr>
<tr>
<th>E-Mail</th>
@@ -50,10 +58,28 @@
<th>Zeitraum</th>
<td>{dateFrom} - {dateTo}</td>
</tr>
<tr>
<th>Nächte</th>
<td>{nights}</td>
</tr>
<tr>
<th>Anzahl der Personen</th>
<td>{pax -> v:or(alternative: '-')}</td>
</tr>
<tr>
<th>Anzahl Kinder 0-3 Jahre</th>
<td>{children -> v:or(alternative: '-')}</td>
</tr>
<tr>
<th>Anzahl Kinder 4-5 Jahre</th>
<td>{minors -> v:or(alternative: '-')}</td>
</tr>
<f:if condition="{adolescentsAge}">
<tr>
<th>Anzahl Kinder 6-{adolescentsAge} Jahre</th>
<td>{adolescents -> v:or(alternative: '-')}</td>
</tr>
</f:if>
<tr>
<th>Verpflegung</th>
<td>
@@ -78,25 +104,52 @@
</f:for>
</ul>
</f:then>
<f:else>
-
</f:else>
</f:if>
</td>
</tr>
<tr>
<th>Kostenübersicht</th>
<td>
Grundpreis: {summary.paxBase}<br>
Aufpreis Personen: {summary.paxAdditional -> v:or(alternative: '-')}<br>
Verpflegung: {summary.board -> v:or(alternative: '-')}<br>
Kleingruppen-Verpflegungszuschlag: {summary.undersubscription -> v:or(alternative: '-')}<br>
Aufpreis Kurzzeit: {summary.shortTerm -> v:or(alternative: '-')}<br>
Zusatzleistungen: {summary.options}<br>
Strom- und Abfallgebühren: {summary.runningCosts -> v:or(alternative: '-')}<br>
<strong>Gesamtpreis: {summary.total}</strong>
<ul>
<li>
Grundpreis: {summary.paxBase}
</li>
<li>
Aufpreis Personen: {summary.paxAdditional -> v:or(alternative: '-')}
</li>
<li>
Verpflegung: {summary.board -> v:or(alternative: '-')}
</li>
<li>
Kleingruppen-Verpflegungszuschlag: {summary.undersubscription -> v:or(alternative: '-')}
</li>
<li>
Aufpreis Kurzzeit: {summary.shortTerm -> v:or(alternative: '-')}
</li>
<li>
Strom- und Abfallgebühren: {summary.runningCosts -> v:or(alternative: '-')}
</li>
<li>
<strong>Gesamtpreis: {summary.total}</strong>
</li>
</ul>
</td>
</tr>
<tr>
<th>Bemerkungen/Wünsche</th>
<td>{remarks -> f:format.nl2br()}</td>
<td>
<f:if condition="{remarks}">
<f:then>
{remarks -> f:format.nl2br()}
</f:then>
<f:else>
-
</f:else>
</f:if>
</td>
</tr>
</table>
</f:section>
@@ -15,10 +15,10 @@
options-json='{options}'
running-costs-factor-eur="{runningCostsEUR}"
running-costs-factor-chf="{runningCostsCHF}"
undersubscription-eur="{undersubscriptionEUR}"
undersubscription-chf="{undersubscriptionCHF}"
undersubscription-ext-eur="{undersubscriptionExtEUR}"
undersubscription-ext-chf="{undersubscriptionExtCHF}"
undersubscription-30-eur="{undersubscription30EUR}"
undersubscription-30-chf="{undersubscription30CHF}"
undersubscription-40-eur="{undersubscription40EUR}"
undersubscription-40-chf="{undersubscription40CHF}"
country-code="{countryCode}"
endpoint="{ep:uri.ajax(action: 'processForm', controller: 'AjaxGroupsPrice', pageUid: settings.defaultAjaxUid, arguments: '{hotel: hotel}')}"
loader-uri="{f:uri.image(src: 'EXT:ep_theme/Resources/Public/images/ajax-loader-white.gif')}"
@@ -26,6 +26,8 @@
logo-at="{f:uri.image(src: 'fileadmin/user_upload/Logos_Banner/Cllt_Touristik_frei_gestellt.jpg')}"
main-season-from="{mainSeasonFrom}"
main-season-to="{mainSeasonTo}"
tax-label="{hotel.groupsPriceTaxLabel -> v:or(alternative: 'zzgl. vor Ort zu entrichtender Ortstaxe')}"
adolescents-age="{adolescentsAge}"
>
</groups-price-calculator>
</f:format.raw>
@@ -339,7 +339,7 @@
data-lightbox-type-value="external"
data-lightbox-height-value="85vh"
data-action="lightbox#open">
Preisberechnung &amp; direkt buchen
Preisrechner/Buchungstool
</button>
</f:if>
<div class="bg-zinc-50 p-4 flex flex-col space-y-2 relative mb-8">
@@ -6,6 +6,6 @@
<f:section name="Main">
<f:render partial="Travelinfo" section="Main" arguments="{_all}"/>
</f:section>
</f:section>
</html>